mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
refactor(docs): unify homepage icon link tiles (#48317)
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Docs UI refactor. ## What is the current behavior? Framework quickstarts and several other homepage grids used `IconPanel` / `IconPanelWithIconPicker`, which brought redundant tooltips (duplicating the visible title), inconsistent hover treatment, and circular icon tiles that didn't match the intended squarish look. | Before | | --- | | <img width="1744" height="626" alt="CleanShot 2026-07-24 at 14 29 44@2x" src="https://github.com/user-attachments/assets/1e145be2-19c9-4bd0-bf06-17516acbc774" /> | ## What is the new behavior? Introduces a shared `IconLink` used across the docs homepage (and reference index) so icon+label tiles share the same composition and hover: icon tile fill matches the surrounding surface (`surface-100`), with a stronger border on hover, and the row uses `hover:bg-accent`. Framework quickstarts keep the larger tile size; other sections keep the smaller size. Also aligns Explore more GlassPanels with Build your backend by using the same bordered background treatment. | After | | --- | | <img width="2478" height="906" alt="CleanShot 2026-07-24 at 15 09 48@2x" src="https://github.com/user-attachments/assets/dd516845-c162-47e4-8b86-9a7fa0e73290" /> | ## Additional context Removes the unused `IconPanelWithIconPicker` wrapper now that homepage/reference consumers go through `IconLink`. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added reusable `IconLink` UI for consistent docs tiles, including `IconLinkList`, menu icon, and light/dark icon rendering. * Introduced `FrameworkQuickstarts` to generate quickstart links based on feature-flag-enabled SDKs. * **UI / Improvements** * Updated the docs home and API reference pages to use the new list-based icon-link layout. * Refreshed migration guides and “Explore more”/self-hosting sections (including accessibility and updated CTA text). * **Bug Fixes** * Migration guide items now omit entries missing required details. * **Chores / Cleanup** * Removed the older icon panel picker-based UI component and its usage. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
8aeae070c2
commit
4bda3bfe72
6 files changed
+301
-239
No files matched your search
+63
-67
@@ -2,13 +2,12 @@ import { isFeatureEnabled } from 'common'
|
||||
import { type Metadata, type ResolvingMetadata } from 'next'
|
||||
import Link from 'next/link'
|
||||
import { cn } from 'ui'
|
||||
import { IconPanel } from 'ui-patterns/IconPanel'
|
||||
import { TextLink } from 'ui-patterns/TextLink'
|
||||
|
||||
import { FrameworkQuickstarts } from '@/components/HomePageCover'
|
||||
import { FrameworkQuickstarts } from '@/components/FrameworkQuickstarts'
|
||||
import { MIGRATION_PAGES } from '@/components/Navigation/NavigationMenu/NavigationMenu.constants'
|
||||
import { GlassPanelWithIconPicker } from '@/features/ui/GlassPanelWithIconPicker'
|
||||
import { IconPanelWithIconPicker } from '@/features/ui/IconPanelWithIconPicker'
|
||||
import { IconLinkImage, IconLinkList, IconLinkMenuIcon } from '@/features/ui/IconLink'
|
||||
import HomeLayout from '@/layouts/HomeLayout'
|
||||
import { BASE_PATH } from '@/lib/constants'
|
||||
|
||||
@@ -88,31 +87,26 @@ const postgresIntegrations = [
|
||||
title: 'AI & Vectors',
|
||||
icon: 'ai',
|
||||
href: '/guides/ai',
|
||||
description: 'AI toolkit to manage embeddings',
|
||||
},
|
||||
{
|
||||
title: 'Cron',
|
||||
icon: 'cron',
|
||||
href: '/guides/cron',
|
||||
description: 'Schedule and monitor recurring Jobs',
|
||||
},
|
||||
{
|
||||
title: 'Queues',
|
||||
icon: 'queues',
|
||||
href: '/guides/queues',
|
||||
description: 'Durable Message Queues with guaranteed delivery',
|
||||
},
|
||||
{
|
||||
title: 'Data REST API',
|
||||
icon: 'rest',
|
||||
href: '/guides/api',
|
||||
description: 'Access your database through a RESTful API.',
|
||||
},
|
||||
{
|
||||
title: 'GraphQL API',
|
||||
icon: 'graphql',
|
||||
href: '/guides/graphql',
|
||||
description: 'Access your database through a GraphQL API.',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -224,6 +218,20 @@ const additionalResources = [
|
||||
},
|
||||
]
|
||||
|
||||
const migrationGuides = [...MIGRATION_PAGES]
|
||||
.sort((a, b) => (a.name || '').localeCompare(b.name || ''))
|
||||
.flatMap((guide) => {
|
||||
if (!guide.name || !guide.url || typeof guide.icon !== 'string') return []
|
||||
|
||||
return [
|
||||
{
|
||||
title: guide.name,
|
||||
href: guide.url,
|
||||
icon: <IconLinkImage path={guide.icon} hasLightIcon={guide.hasLightIcon} />,
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
const HomePage = () => (
|
||||
<HomeLayout>
|
||||
<div className="flex flex-col">
|
||||
@@ -234,12 +242,12 @@ const HomePage = () => (
|
||||
Connect a framework
|
||||
</h2>
|
||||
<p className="m-0 p-0 text-sm text-foreground-light">
|
||||
Start with a framework quickstart and connect your project in minutes.
|
||||
Start with a quickstart guide to connect your project in minutes.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="col-span-8 not-prose">
|
||||
<FrameworkQuickstarts />
|
||||
<FrameworkQuickstarts labelledBy="connect-a-framework" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -253,7 +261,10 @@ const HomePage = () => (
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ul className="col-span-8 grid grid-cols-12 gap-6 not-prose [&_svg]:text-brand-600">
|
||||
<ul
|
||||
aria-labelledby="products"
|
||||
className="col-span-8 grid grid-cols-12 gap-6 not-prose [&_svg]:text-brand-600"
|
||||
>
|
||||
{products.map((product) => {
|
||||
return (
|
||||
<li key={product.title} className={cn(product.span ?? 'col-span-12 md:col-span-6')}>
|
||||
@@ -277,18 +288,15 @@ const HomePage = () => (
|
||||
Extend your database with built-in tools for AI, APIs, scheduled jobs, and queues.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid col-span-8 grid-cols-12 gap-6 not-prose">
|
||||
{postgresIntegrations.map((integration) => (
|
||||
<Link
|
||||
href={integration.href}
|
||||
key={integration.title}
|
||||
passHref
|
||||
className="col-span-6 md:col-span-4"
|
||||
>
|
||||
<IconPanelWithIconPicker {...integration} />
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<IconLinkList
|
||||
labelledBy="postgres-integrations"
|
||||
className="col-span-8 not-prose"
|
||||
items={postgresIntegrations.map((integration) => ({
|
||||
title: integration.title,
|
||||
href: integration.href,
|
||||
icon: <IconLinkMenuIcon icon={integration.icon} />,
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-6 border-b py-12 lg:grid lg:grid-cols-12 lg:gap-x-16">
|
||||
@@ -301,23 +309,17 @@ const HomePage = () => (
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid col-span-8 grid-cols-12 gap-6 not-prose">
|
||||
{clientLibraries
|
||||
<IconLinkList
|
||||
labelledBy="client-libraries"
|
||||
className="col-span-8 not-prose"
|
||||
items={clientLibraries
|
||||
.filter((library) => library.enabled)
|
||||
|
||||
.map((library) => {
|
||||
return (
|
||||
<Link
|
||||
href={library.href}
|
||||
key={library.title}
|
||||
passHref
|
||||
className="col-span-6 md:col-span-4"
|
||||
>
|
||||
<IconPanelWithIconPicker {...library} />
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
.map((library) => ({
|
||||
title: library.title,
|
||||
href: library.href,
|
||||
icon: <IconLinkMenuIcon icon={library.icon} />,
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
{isFeatureEnabled('docs:full_getting_started') && (
|
||||
<div className="flex flex-col gap-6 border-b py-12 lg:grid lg:grid-cols-12 lg:gap-x-16">
|
||||
@@ -335,19 +337,11 @@ const HomePage = () => (
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ul className="grid col-span-8 grid-cols-12 gap-6 not-prose">
|
||||
{MIGRATION_PAGES.sort((a, b) => (a.name || '').localeCompare(b.name || '')).map(
|
||||
(guide) => {
|
||||
return (
|
||||
<li key={guide.name} className="col-span-6 md:col-span-4">
|
||||
<Link href={guide.url || '#'} passHref>
|
||||
<IconPanel {...guide} title={guide.name} background={true} showLink={false} />
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</ul>
|
||||
<IconLinkList
|
||||
labelledBy="migrate-to-supabase"
|
||||
className="col-span-8 not-prose"
|
||||
items={migrationGuides}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -361,7 +355,10 @@ const HomePage = () => (
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ul className="col-span-8 grid grid-cols-12 gap-6 not-prose">
|
||||
<ul
|
||||
aria-labelledby="additional-resources"
|
||||
className="col-span-8 grid grid-cols-12 gap-6 not-prose"
|
||||
>
|
||||
{additionalResources.map((resource) => {
|
||||
return (
|
||||
<li key={resource.title} className="col-span-12 md:col-span-6">
|
||||
@@ -370,7 +367,7 @@ const HomePage = () => (
|
||||
passHref
|
||||
target={resource.external ? '_blank' : undefined}
|
||||
>
|
||||
<GlassPanelWithIconPicker {...resource} background={false}>
|
||||
<GlassPanelWithIconPicker {...resource}>
|
||||
{resource.description}
|
||||
</GlassPanelWithIconPicker>
|
||||
</Link>
|
||||
@@ -392,25 +389,24 @@ const HomePage = () => (
|
||||
Get started with self-hosting Supabase.
|
||||
</p>
|
||||
<TextLink
|
||||
label="More on Self-Hosting"
|
||||
label="More on self-hosting"
|
||||
url="/guides/self-hosting"
|
||||
className="no-underline text-brand-link text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid col-span-8 grid-cols-12 gap-6 not-prose">
|
||||
<ul className="col-span-full lg:col-span-8 grid grid-cols-12 gap-6">
|
||||
{selfHostingOptions.map((option) => {
|
||||
return (
|
||||
<li key={option.title} className="col-span-6">
|
||||
<Link href={option.href} passHref>
|
||||
<IconPanelWithIconPicker {...option} background={true} showLink={false} />
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
<div className="col-span-8 grid grid-cols-12 not-prose">
|
||||
<IconLinkList
|
||||
labelledBy="self-hosting"
|
||||
className="col-span-full lg:col-span-8"
|
||||
itemClassName="col-span-6"
|
||||
items={selfHostingOptions.map((option) => ({
|
||||
title: option.title,
|
||||
href: option.href,
|
||||
icon: <IconLinkMenuIcon icon={option.icon} />,
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import Link from 'next/link'
|
||||
|
||||
import { REFERENCES, clientSdkIds } from '~/content/navigation.references'
|
||||
import { IconPanelWithIconPicker } from '~/features/ui/IconPanelWithIconPicker'
|
||||
import { clientSdkIds, REFERENCES } from '~/content/navigation.references'
|
||||
import { IconLinkList, IconLinkMenuIcon } from '~/features/ui/IconLink'
|
||||
import { LayoutMainContent } from '~/layouts/DefaultLayout'
|
||||
import { SidebarSkeleton } from '~/layouts/MainSkeleton'
|
||||
|
||||
@@ -20,41 +18,37 @@ export default function ReferenceIndexPage() {
|
||||
Supabase also has a Management API to help with managing your Supabase Platform, and a
|
||||
CLI for local development and CI workflows.
|
||||
</p>
|
||||
<h2 className="mb-8">Client Libraries</h2>
|
||||
<div className="grid col-span-8 grid-cols-12 gap-6 not-prose">
|
||||
{clientSdkIds.map((sdkId) => {
|
||||
return (
|
||||
<Link
|
||||
key={REFERENCES[sdkId].name}
|
||||
href={`/reference/${REFERENCES[sdkId].libPath}`}
|
||||
passHref
|
||||
className="col-span-6 md:col-span-4"
|
||||
>
|
||||
<IconPanelWithIconPicker
|
||||
title={REFERENCES[sdkId].name}
|
||||
icon={REFERENCES[sdkId].icon}
|
||||
/>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<h2 className="mb-8">Management API and CLI</h2>
|
||||
<div className="grid col-span-8 grid-cols-12 gap-6 not-prose">
|
||||
<Link
|
||||
href={`/reference/api/introduction`}
|
||||
passHref
|
||||
className="col-span-6 md:col-span-4"
|
||||
>
|
||||
<IconPanelWithIconPicker title="Management API" icon={REFERENCES['api'].icon} />
|
||||
</Link>
|
||||
<Link
|
||||
href={`/reference/cli/introduction`}
|
||||
passHref
|
||||
className="col-span-6 md:col-span-4"
|
||||
>
|
||||
<IconPanelWithIconPicker title="CLI" icon={REFERENCES['cli'].icon} />
|
||||
</Link>
|
||||
</div>
|
||||
<h2 id="client-libraries" className="mb-8">
|
||||
Client Libraries
|
||||
</h2>
|
||||
<IconLinkList
|
||||
labelledBy="client-libraries"
|
||||
className="not-prose"
|
||||
items={clientSdkIds.map((sdkId) => ({
|
||||
title: REFERENCES[sdkId].name,
|
||||
href: `/reference/${REFERENCES[sdkId].libPath}`,
|
||||
icon: <IconLinkMenuIcon icon={REFERENCES[sdkId].icon} />,
|
||||
}))}
|
||||
/>
|
||||
<h2 id="management-api-and-cli" className="mb-8">
|
||||
Management API and CLI
|
||||
</h2>
|
||||
<IconLinkList
|
||||
labelledBy="management-api-and-cli"
|
||||
className="not-prose"
|
||||
items={[
|
||||
{
|
||||
title: 'Management API',
|
||||
href: '/reference/api/introduction',
|
||||
icon: <IconLinkMenuIcon icon={REFERENCES['api'].icon} />,
|
||||
},
|
||||
{
|
||||
title: 'CLI',
|
||||
href: '/reference/cli/introduction',
|
||||
icon: <IconLinkMenuIcon icon={REFERENCES['cli'].icon} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</article>
|
||||
</LayoutMainContent>
|
||||
</SidebarSkeleton>
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import { isFeatureEnabled } from 'common'
|
||||
|
||||
import { IconLinkImage, IconLinkList } from '@/features/ui/IconLink'
|
||||
|
||||
const {
|
||||
sdkDart: sdkDartEnabled,
|
||||
sdkKotlin: sdkKotlinEnabled,
|
||||
sdkSwift: sdkSwiftEnabled,
|
||||
} = isFeatureEnabled(['sdk:dart', 'sdk:kotlin', 'sdk:swift'])
|
||||
|
||||
const frameworks = [
|
||||
{
|
||||
name: 'React',
|
||||
icon: '/docs/img/icons/react-icon',
|
||||
href: '/guides/getting-started/quickstarts/reactjs',
|
||||
},
|
||||
{
|
||||
name: 'Next.js',
|
||||
icon: '/docs/img/icons/nextjs-icon',
|
||||
href: '/guides/getting-started/quickstarts/nextjs',
|
||||
hasLightIcon: true,
|
||||
},
|
||||
{
|
||||
name: 'TanStack Start',
|
||||
icon: '/docs/img/icons/tanstack-icon',
|
||||
href: '/guides/getting-started/quickstarts/tanstack',
|
||||
hasLightIcon: true,
|
||||
},
|
||||
{
|
||||
name: 'Astro',
|
||||
icon: '/docs/img/icons/astro-icon',
|
||||
href: '/guides/getting-started/quickstarts/astrojs',
|
||||
hasLightIcon: true,
|
||||
},
|
||||
{
|
||||
name: 'Vue',
|
||||
icon: '/docs/img/icons/vuejs-icon',
|
||||
href: '/guides/getting-started/quickstarts/vue',
|
||||
},
|
||||
{
|
||||
name: 'Nuxt',
|
||||
icon: '/docs/img/icons/nuxt-icon',
|
||||
href: '/guides/getting-started/quickstarts/nuxtjs',
|
||||
},
|
||||
{
|
||||
name: 'iOS Swift',
|
||||
icon: '/docs/img/icons/swift-icon-orange',
|
||||
href: '/guides/getting-started/quickstarts/ios-swiftui',
|
||||
enabled: sdkSwiftEnabled,
|
||||
},
|
||||
{
|
||||
name: 'Android Kotlin',
|
||||
icon: '/docs/img/icons/kotlin-icon',
|
||||
href: '/guides/getting-started/quickstarts/kotlin',
|
||||
enabled: sdkKotlinEnabled,
|
||||
},
|
||||
{
|
||||
name: 'Expo React Native',
|
||||
icon: '/docs/img/icons/expo-icon',
|
||||
href: '/guides/getting-started/quickstarts/expo-react-native',
|
||||
hasLightIcon: true,
|
||||
},
|
||||
{
|
||||
name: 'Flutter',
|
||||
icon: '/docs/img/icons/flutter-icon',
|
||||
href: '/guides/getting-started/quickstarts/flutter',
|
||||
enabled: sdkDartEnabled,
|
||||
},
|
||||
{
|
||||
name: 'Python',
|
||||
icon: '/docs/img/icons/python-icon',
|
||||
href: '/guides/getting-started/quickstarts/flask',
|
||||
},
|
||||
]
|
||||
|
||||
export function FrameworkQuickstarts({ labelledBy }: { labelledBy?: string }) {
|
||||
return (
|
||||
<IconLinkList
|
||||
labelledBy={labelledBy}
|
||||
size="lg"
|
||||
items={frameworks
|
||||
.filter((framework) => framework.enabled !== false)
|
||||
.map((framework) => ({
|
||||
title: framework.name,
|
||||
href: framework.href,
|
||||
icon: (
|
||||
<IconLinkImage path={framework.icon} hasLightIcon={framework.hasLightIcon} size="lg" />
|
||||
),
|
||||
}))}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -7,22 +7,14 @@ import {
|
||||
PromptPanel,
|
||||
PromptTitle,
|
||||
} from '~/features/ui/PromptPanel'
|
||||
import { isFeatureEnabled, useBreakpoint } from 'common'
|
||||
import { isFeatureEnabled } from 'common'
|
||||
import { Sparkles, Terminal } from 'lucide-react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import Link from 'next/link'
|
||||
import { useEffect, useState, type ReactNode } from 'react'
|
||||
import { IconPanel } from 'ui-patterns/IconPanel'
|
||||
import { type ReactNode } from 'react'
|
||||
|
||||
import { getCustomContent } from '../lib/custom-content/getCustomContent'
|
||||
import DocsCoverLogo from './DocsCoverLogo'
|
||||
import { setupCommand, setupCommands, setupPrompt } from './HomePageCover.constants'
|
||||
|
||||
const {
|
||||
sdkDart: sdkDartEnabled,
|
||||
sdkKotlin: sdkKotlinEnabled,
|
||||
sdkSwift: sdkSwiftEnabled,
|
||||
} = isFeatureEnabled(['sdk:dart', 'sdk:kotlin', 'sdk:swift'])
|
||||
const fullGettingStartedEnabled = isFeatureEnabled('docs:full_getting_started')
|
||||
|
||||
function SetupPrompt({ cliCode }: { cliCode: ReactNode }) {
|
||||
@@ -58,115 +50,6 @@ function SetupPrompt({ cliCode }: { cliCode: ReactNode }) {
|
||||
)
|
||||
}
|
||||
|
||||
const frameworks = [
|
||||
{
|
||||
tooltip: 'ReactJS',
|
||||
icon: '/docs/img/icons/react-icon',
|
||||
href: '/guides/getting-started/quickstarts/reactjs',
|
||||
hasLightIcon: false,
|
||||
},
|
||||
{
|
||||
tooltip: 'Next.js',
|
||||
icon: '/docs/img/icons/nextjs-icon',
|
||||
href: '/guides/getting-started/quickstarts/nextjs',
|
||||
hasLightIcon: false,
|
||||
},
|
||||
{
|
||||
tooltip: 'TanStack Start',
|
||||
icon: '/docs/img/icons/tanstack-icon',
|
||||
href: '/guides/getting-started/quickstarts/tanstack',
|
||||
hasLightIcon: true,
|
||||
},
|
||||
{
|
||||
tooltip: 'Astro.js',
|
||||
icon: '/docs/img/icons/astro-icon',
|
||||
href: '/guides/getting-started/quickstarts/astrojs',
|
||||
hasLightIcon: true,
|
||||
},
|
||||
{
|
||||
tooltip: 'Vue',
|
||||
icon: '/docs/img/icons/vuejs-icon',
|
||||
href: '/guides/getting-started/quickstarts/vue',
|
||||
hasLightIcon: false,
|
||||
},
|
||||
{
|
||||
tooltip: 'Nuxt',
|
||||
icon: '/docs/img/icons/nuxt-icon',
|
||||
href: '/guides/getting-started/quickstarts/nuxtjs',
|
||||
hasLightIcon: false,
|
||||
},
|
||||
{
|
||||
tooltip: 'iOS Swift',
|
||||
icon: '/docs/img/icons/swift-icon-orange',
|
||||
href: '/guides/getting-started/quickstarts/ios-swiftui',
|
||||
enabled: sdkSwiftEnabled,
|
||||
hasLightIcon: false,
|
||||
},
|
||||
{
|
||||
tooltip: 'Android Kotlin',
|
||||
icon: '/docs/img/icons/kotlin-icon',
|
||||
href: '/guides/getting-started/quickstarts/kotlin',
|
||||
enabled: sdkKotlinEnabled,
|
||||
hasLightIcon: false,
|
||||
},
|
||||
{
|
||||
tooltip: 'Expo React Native',
|
||||
icon: '/docs/img/icons/expo-icon',
|
||||
href: '/guides/getting-started/quickstarts/expo-react-native',
|
||||
hasLightIcon: true,
|
||||
},
|
||||
{
|
||||
tooltip: 'Flutter',
|
||||
icon: '/docs/img/icons/flutter-icon',
|
||||
href: '/guides/getting-started/quickstarts/flutter',
|
||||
enabled: sdkDartEnabled,
|
||||
hasLightIcon: false,
|
||||
},
|
||||
{
|
||||
tooltip: 'Python',
|
||||
icon: '/docs/img/icons/python-icon',
|
||||
href: '/guides/getting-started/quickstarts/flask',
|
||||
hasLightIcon: false,
|
||||
},
|
||||
]
|
||||
|
||||
export function FrameworkQuickstarts() {
|
||||
const isXs = useBreakpoint(639)
|
||||
const iconSize = isXs ? 'sm' : 'lg'
|
||||
const { resolvedTheme } = useTheme()
|
||||
const [isMounted, setIsMounted] = useState(false)
|
||||
const isLightMode = isMounted && resolvedTheme === 'light'
|
||||
|
||||
useEffect(() => setIsMounted(true), [])
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-12 gap-6">
|
||||
{frameworks
|
||||
.filter((framework) => framework.enabled !== false)
|
||||
.map((framework) => {
|
||||
const iconToUse =
|
||||
framework.hasLightIcon && isLightMode ? `${framework.icon}-light` : framework.icon
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={framework.tooltip}
|
||||
href={framework.href}
|
||||
passHref
|
||||
className="col-span-6 no-underline md:col-span-4"
|
||||
>
|
||||
<IconPanel
|
||||
iconSize={iconSize}
|
||||
tooltip={framework.tooltip}
|
||||
title={framework.tooltip}
|
||||
icon={iconToUse}
|
||||
/>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const HomePageCover = ({ title, cliCode }: { title: string; cliCode: ReactNode }) => {
|
||||
const { homepageHeading } = getCustomContent(['homepage:heading'])
|
||||
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import MenuIconPicker from '~/components/Navigation/NavigationMenu/MenuIconPicker'
|
||||
import Link from 'next/link'
|
||||
import { type ReactNode } from 'react'
|
||||
import { cn } from 'ui'
|
||||
|
||||
const sizeStyles = {
|
||||
sm: {
|
||||
tile: 'h-10 w-10',
|
||||
image: 'w-5',
|
||||
},
|
||||
lg: {
|
||||
tile: 'h-10 w-10 sm:h-16 sm:w-16',
|
||||
image: 'w-5 sm:w-8',
|
||||
},
|
||||
} as const
|
||||
|
||||
export type IconLinkSize = keyof typeof sizeStyles
|
||||
|
||||
export type IconLinkItem = {
|
||||
title: string
|
||||
href: string
|
||||
icon: ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function IconLink({
|
||||
href,
|
||||
title,
|
||||
icon,
|
||||
size = 'sm',
|
||||
className,
|
||||
}: {
|
||||
href: string
|
||||
title: string
|
||||
icon: ReactNode
|
||||
size?: IconLinkSize
|
||||
className?: string
|
||||
}) {
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
className={cn(
|
||||
'group relative -m-3 flex items-center gap-3 rounded-xl p-3 no-underline transition-colors hover:bg-accent',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'flex shrink-0 items-center justify-center overflow-hidden rounded-lg border bg-surface-100 transition-colors group-hover:border-strong',
|
||||
sizeStyles[size].tile
|
||||
)}
|
||||
>
|
||||
{icon}
|
||||
</div>
|
||||
<span className="text-base text-foreground">{title}</span>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
export function IconLinkList({
|
||||
items,
|
||||
labelledBy,
|
||||
className,
|
||||
itemClassName = 'col-span-6 md:col-span-4',
|
||||
size = 'sm',
|
||||
}: {
|
||||
items: IconLinkItem[]
|
||||
labelledBy?: string
|
||||
className?: string
|
||||
itemClassName?: string
|
||||
size?: IconLinkSize
|
||||
}) {
|
||||
return (
|
||||
<ul className={cn('grid grid-cols-12 gap-6', className)} aria-labelledby={labelledBy}>
|
||||
{items.map((item) => (
|
||||
<li key={item.href} className={cn(itemClassName, item.className)}>
|
||||
<IconLink href={item.href} title={item.title} icon={item.icon} size={size} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
export function IconLinkImage({
|
||||
path,
|
||||
hasLightIcon,
|
||||
size = 'sm',
|
||||
}: {
|
||||
path: string
|
||||
hasLightIcon?: boolean
|
||||
size?: IconLinkSize
|
||||
}) {
|
||||
const imgClassName = sizeStyles[size].image
|
||||
|
||||
return (
|
||||
<>
|
||||
{hasLightIcon ? (
|
||||
<img src={`${path}-light.svg`} alt="" className={cn(imgClassName, 'dark:hidden')} />
|
||||
) : null}
|
||||
<img
|
||||
src={`${path}.svg`}
|
||||
alt=""
|
||||
className={cn(imgClassName, hasLightIcon && 'hidden dark:block')}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function IconLinkMenuIcon({ icon }: { icon: string }) {
|
||||
return <MenuIconPicker icon={icon} width={18} height={18} />
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { type ComponentProps } from 'react'
|
||||
|
||||
import { IconPanel } from 'ui-patterns/IconPanel'
|
||||
import MenuIconPicker from '~/components/Navigation/NavigationMenu/MenuIconPicker'
|
||||
|
||||
type IconPanelWithIconPickerProps = Omit<ComponentProps<typeof IconPanel>, 'icon'> & {
|
||||
icon: string
|
||||
}
|
||||
|
||||
function IconPanelWithIconPicker({ icon, ...props }: IconPanelWithIconPickerProps) {
|
||||
return <IconPanel icon={<MenuIconPicker icon={icon} width={18} height={18} />} {...props} />
|
||||
}
|
||||
|
||||
export { IconPanelWithIconPicker }
|
||||
Reference in new issue
Block a user