mirror of
https://github.com/supabase/supabase.git
synced 2026-09-06 18:11:51 +08:00
## What kind of change does this PR introduce?
Bug fix / docs UI polish.
## What is the current behavior?
- Many docs `GlassPanel`s use `background={false}`, so hover only tweaks
the border and reads as having no hover state
- Compact icon+label grids still use `IconPanel`, which has a broken
`-z-10` hover fill and overlaps with the newer `IconLink` pattern
- Description card grids jump to 3-up too early on medium widths
## What is the new behavior?
**GlassPanel**
- Removes the `background` prop; cards always use the filled surface
with stronger border hover
- Tightens icon→description gap (`gap-6` → `gap-3`)
- Decorative icons/logos use empty `alt` so screen readers don’t hear
the title twice
**Icon tiles**
- Retires `IconPanel` from docs and deletes it from `ui-patterns`
- Uses `IconLink` / `IconLinkList` for compact navigation tiles (auth
providers, social login, etc.)
- Adds `IconLinkButton` for SMS provider pickers (same chrome, opens a
dialog)
- Adds focus styles, list labelling, and dialog-trigger ARIA where
needed
**Layout / content**
- Migrate-to-Supabase description cards on resources use `GlassPanel`
(not slim icon tiles)
- Grid spans use `md:… xl:…` so cards stay 2-up until ~1280px
- Fixes migrate links to `/guides/platform/migrating-to-supabase/…` and
SSR quickstarts to `creating-a-client` with framework query params
- Moves the Extensions list `key` onto the outer `Link`
| Before | After |
| --- | --- |
| <img width="1185" height="1323" alt="Resources Supabase Docs"
src="https://github.com/user-attachments/assets/1677bf65-d3a3-4202-8c70-e758f7c3bcce"
/> | <img width="1185" height="1323" alt="Resources Supabase Docs"
src="https://github.com/user-attachments/assets/51760f0f-62b6-4010-9841-de26039f37b4"
/> |
## Additional context
Homepage compact sections already use `IconLinkList` from #48317; this
PR finishes that pattern for remaining docs `IconPanel` callsites and
cleans up GlassPanel hover.
`www/customers` only drops the removed `background` prop; those cards
already use the filled surface via `logo`.
## Test plan
- [ ] `/guides/getting-started`: GlassPanels show filled surface and
clearer border hover
- [ ] `/guides/resources`: migrate cards are GlassPanels with working
`/platform/…` links; 2-up until xl
- [ ] `/guides/auth/social-login` and auth providers partial: IconLink
tiles hover/focus correctly
- [ ] `/guides/auth/phone-login`: SMS provider buttons open dialogs;
keyboard focus works
- [ ] Docs homepage: migrate / self-host IconLinkLists unchanged in
behaviour
- [ ] `/guides/auth/server-side`: Next.js / SvelteKit cards resolve on
docs preview
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Improved Layouts**
* Made “GlassPanel” card grids more responsive and consistent; refined
card and success badge spacing for a cleaner presentation.
* **Updated Documentation**
* Refreshed multiple guide and resource pages (including quickstarts and
migration content) with standardized card layouts and updated link
destinations.
* **Component Updates**
* Standardized “GlassPanel” styling (background toggle removed) and
simplified icon-based panels; added an `IconLinkButton` for action
tiles; updated authentication provider grids to use the shared tile UI.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
160 lines
3.4 KiB
TypeScript
160 lines
3.4 KiB
TypeScript
import MenuIconPicker from '~/components/Navigation/NavigationMenu/MenuIconPicker'
|
||
import Link from 'next/link'
|
||
import { type ButtonHTMLAttributes, 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
|
||
}
|
||
|
||
const iconLinkClassName =
|
||
'group relative -m-3 flex items-center gap-3 rounded-xl p-3 no-underline transition-colors hover:bg-accent focus-ring focus-visible:bg-accent'
|
||
|
||
function IconLinkContent({
|
||
title,
|
||
icon,
|
||
size = 'sm',
|
||
}: {
|
||
title: string
|
||
icon: ReactNode
|
||
size?: IconLinkSize
|
||
}) {
|
||
return (
|
||
<>
|
||
<div
|
||
className={cn(
|
||
'flex shrink-0 items-center justify-center overflow-hidden rounded-lg border bg-surface-100 transition-colors group-hover:border-strong group-focus-visible:border-strong',
|
||
sizeStyles[size].tile
|
||
)}
|
||
>
|
||
{icon}
|
||
</div>
|
||
<span className="text-base text-foreground">{title}</span>
|
||
</>
|
||
)
|
||
}
|
||
|
||
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(iconLinkClassName, className)}>
|
||
<IconLinkContent title={title} icon={icon} size={size} />
|
||
</Link>
|
||
)
|
||
}
|
||
|
||
export function IconLinkButton({
|
||
title,
|
||
icon,
|
||
size = 'sm',
|
||
className,
|
||
disabled,
|
||
tabIndex,
|
||
...props
|
||
}: {
|
||
title: string
|
||
icon: ReactNode
|
||
size?: IconLinkSize
|
||
className?: string
|
||
} & ButtonHTMLAttributes<HTMLButtonElement>) {
|
||
return (
|
||
<button
|
||
type="button"
|
||
disabled={disabled}
|
||
tabIndex={tabIndex ?? (disabled ? -1 : 0)}
|
||
className={cn(iconLinkClassName, 'w-full text-left', className)}
|
||
{...props}
|
||
>
|
||
<IconLinkContent title={title} icon={icon} size={size} />
|
||
</button>
|
||
)
|
||
}
|
||
|
||
export function IconLinkList({
|
||
items,
|
||
labelledBy,
|
||
label,
|
||
className,
|
||
itemClassName = 'col-span-6 md:col-span-4',
|
||
size = 'sm',
|
||
}: {
|
||
items: IconLinkItem[]
|
||
/** Prefer when a visible heading is the list’s name. */
|
||
labelledBy?: string
|
||
/** Prefer when the nearby heading is section/setup copy, not a list title. */
|
||
label?: string
|
||
className?: string
|
||
itemClassName?: string
|
||
size?: IconLinkSize
|
||
}) {
|
||
return (
|
||
<ul
|
||
className={cn('grid grid-cols-12 gap-6', className)}
|
||
aria-labelledby={labelledBy}
|
||
aria-label={label}
|
||
>
|
||
{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} />
|
||
}
|