mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
chore(a11y): convert leftover focus recipes to focus-ring (#48219)
## What kind of change does this PR introduce? Accessibility cleanup (DEPR-628). ## What is the current behavior? Leftover call sites still use ad-hoc focus recipes (`ring-foreground-muted`, `outline-brand`, Dialog/Sheet `focus:` rings, etc.) instead of the shared utilities from #41575. ## What is the new behavior? Converts those leftovers across `packages/ui`, Studio, www, docs, and design-system to `focus-ring`, preferring `focus-visible`. Keeps documented exceptions (`group-focus-visible`, InputGroup `:has()`). ## To test Tab through controls (keyboard only). Expect a consistent offset ring on `:focus-visible`, not a green/brand/custom stack, and no ring animation. ### www (marketing) Preview: https://zone-www-dot-com-git-danny-depr-628-focus-ring-fbccf9-supabase.vercel.app - Global nav on `/`: Product, Developers, Solutions dropdowns; logo; hamburger + mobile menu - `/features`: view toggles and feature cards - `/company`: card links - `/changelog`: timeline / entry links - `/partners/catalog`: grid/list toggle and partner cards - `/pricing`: compute section expand control - Product / Modules / Solutions sticky navs on product pages (e.g. `/database`, `/storage`) - `/state-of-startups`: TwoOptionToggle if present ### docs Preview: https://docs-git-danny-depr-628-focus-ring-long-tail-supabase.vercel.app - Any guide page: top nav dropdowns and items - Narrow viewport: hamburger, then mobile menu links + close - Guide with PromptPanel / tabs: tab to prompt actions and tab list ### studio (dashboard) Preview: https://studio-staging-git-danny-depr-628-focus-ring-long-tail-supabase.vercel.app - Project home: Connect section tiles; drag-handle focus on sortable sections - Integrations marketplace (`/project/<ref>/integrations`): featured cards, list/grid toggle, list rows - Auth (`/project/<ref>/auth/oauth-apps`, `/project/<ref>/auth/providers`): open create/edit sheet, tab to close (X) - Database policies (`/project/<ref>/database/policies`): open policy editor sheet, tab to close - Storage policies (`/project/<ref>/storage/files/policies`): bucket section links; policy modal close - Query performance (`/project/<ref>/observability/query-performance`): info icon buttons on metrics - Replication pipeline detail (if available): slot lag / status info icons - Support (`/support/new`): attachment add/remove controls - Table editor: spreadsheet import preview checkboxes; row text/JSON editor TwoOptionToggle - Any Dialog/Sheet/toast close (X): ring on keyboard focus only, not mouse click ### design-system Preview: https://design-system-git-danny-depr-628-focus-ring-long-tail-supabase.vercel.app - Colour palette swatches (keyboard focus) - Form patterns sidepanel example: avatar / focusable control in the example ## Additional context - Linear: [DEPR-628](https://linear.app/supabase/issue/DEPR-628) - Follow-ups: form-group CSS (DEPR-629), Storage columns selection (DEPR-630), ESLint rule (DEPR-632) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Accessibility & Usability** * Standardized keyboard focus indicators across navigation, dialogs, forms, buttons, toggles, links, and tooltips using a consolidated focus style. * Improved toggle controls to use proper button semantics (instead of clickable text), including `aria-pressed`/disabled handling and better keyboard navigation. * **Visual Updates** * Harmonized hover/focus ring visuals across the design system, Studio, documentation, and marketing pages while preserving existing layout and interaction behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
0bef8e7d90
commit
3bca21b3f8
53 files changed
+142
-142
No files matched your search
@@ -64,7 +64,7 @@ const ColorPalette = () => {
|
||||
key={step * 100}
|
||||
type="button"
|
||||
onClick={() => handleCopy(reference)}
|
||||
className="group relative flex aspect-square w-full items-center justify-center rounded-sm border border-overlay/40 transition hover:scale-[1.05] focus:outline-none focus-visible:ring-2 focus-visible:ring-foreground"
|
||||
className="group relative flex aspect-square w-full items-center justify-center rounded-sm border border-overlay/40 transition-transform hover:scale-[1.05] focus-ring"
|
||||
style={{ backgroundColor: reference }}
|
||||
title={reference}
|
||||
>
|
||||
|
||||
@@ -319,7 +319,7 @@ export default function FormPatternsSidePanel() {
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
onClick={() => uploadButtonRef.current?.click()}
|
||||
className="flex items-center justify-center h-10 w-10 shrink-0 text-foreground-lighter hover:text-foreground-light overflow-hidden rounded-full bg-cover border hover:border-strong focus-visible:outline-brand-600"
|
||||
className="flex items-center justify-center h-10 w-10 shrink-0 text-foreground-lighter hover:text-foreground-light overflow-hidden rounded-full bg-cover border hover:border-strong focus-ring"
|
||||
style={{
|
||||
backgroundImage: logoUrl ? `url("${logoUrl}")` : 'none',
|
||||
}}
|
||||
|
||||
@@ -29,7 +29,7 @@ const listItem = {
|
||||
}
|
||||
|
||||
const itemClassName =
|
||||
'block py-2 pl-2 pr-3.5 text-sm text-foreground-light hover:bg-surface-200 focus-visible:ring-2 focus-visible:outline-hidden focus-visible:ring-foreground-lighter focus-visible:rounded-sm'
|
||||
'block py-2 pl-2 pr-3.5 text-sm text-foreground-light hover:bg-surface-200 focus-ring rounded-sm'
|
||||
|
||||
const AccordionMenuItem = ({ section }: { section: DropdownMenuItem[] }) => {
|
||||
const activeLabel = useActiveMenuLabel(GLOBAL_MENU_ITEMS)
|
||||
@@ -145,7 +145,7 @@ const GlobalMobileMenu = ({ open, setOpen }: Props) => {
|
||||
tabIndex={0}
|
||||
onClick={() => setOpen(false)}
|
||||
type="button"
|
||||
className="inline-flex items-center justify-center focus:ring-brand bg-surface-100 hover:bg-surface-200 focus:outline-hidden focus:ring-2 focus:ring-inset border border-default bg-surface-100/75 text-foreground-light rounded-sm min-w-[30px] w-[30px] h-[30px]"
|
||||
className="inline-flex items-center justify-center bg-surface-100 hover:bg-surface-200 border border-default bg-surface-100/75 text-foreground-light rounded-sm min-w-[30px] w-[30px] h-[30px] focus-ring"
|
||||
>
|
||||
<span className="sr-only">Close menu</span>
|
||||
<X />
|
||||
|
||||
@@ -59,7 +59,7 @@ export const useActiveMenuLabel = (menu: typeof GLOBAL_MENU_ITEMS) => {
|
||||
const GlobalNavigationMenu: FC = () => {
|
||||
const activeLabel = useActiveMenuLabel(GLOBAL_MENU_ITEMS)
|
||||
const triggerClassName =
|
||||
'h-(--header-height) p-2 bg-transparent border-0 border-b-2 border-transparent font-normal rounded-none text-foreground-light hover:bg-transparent hover:text-foreground data-open:bg-transparent! data-open:text-foreground! data-radix-collection-item:focus-visible:ring-2 data-radix-collection-item:focus-visible:ring-foreground-lighter data-radix-collection-item:focus-visible:text-foreground h-full focus-visible:rounded-sm shadow-none! outline-hidden transition-all outline-0 focus-visible:outline-4 focus-visible:outline-offset-1 focus-visible:outline-brand-600'
|
||||
'h-(--header-height) p-2 bg-transparent border-0 border-b-2 border-transparent font-normal rounded-none text-foreground-light hover:bg-transparent hover:text-foreground data-open:bg-transparent! data-open:text-foreground! focus-ring focus-visible:text-foreground h-full focus-visible:rounded-sm shadow-none!'
|
||||
|
||||
return (
|
||||
<div className="flex relative gap-2 justify-start items-end w-full h-full">
|
||||
@@ -172,7 +172,7 @@ export const MenuItem = React.forwardRef<
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'group/menu-item flex items-center gap-2',
|
||||
'w-full flex h-8 items-center text-foreground-light text-sm hover:text-foreground select-none rounded-md p-2 leading-none no-underline outline-hidden! focus-visible:ring-2 focus-visible:ring-foreground-lighter focus-visible:text-foreground',
|
||||
'w-full flex h-8 items-center text-foreground-light text-sm hover:text-foreground select-none rounded-md p-2 leading-none no-underline focus-ring focus-visible:text-foreground',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -133,7 +133,7 @@ function ExpandableContent({ children }: { children: ReactNode }) {
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
onClick={() => setIsExpanded((expanded) => !expanded)}
|
||||
className="mt-2 text-sm text-brand-link transition-colors hover:text-brand focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-foreground-muted"
|
||||
className="mt-2 text-sm text-brand-link transition-colors hover:text-brand focus-ring"
|
||||
aria-expanded={isExpanded}
|
||||
>
|
||||
{isExpanded ? 'Show less' : 'Show more'}
|
||||
@@ -192,7 +192,7 @@ function CopyButton({ label, value }: { label: string; value: string }) {
|
||||
setCopied(true)
|
||||
})
|
||||
}}
|
||||
className="rounded-sm p-1.5 text-foreground-muted transition-colors hover:bg-surface-200 hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-foreground-muted"
|
||||
className="rounded-sm p-1.5 text-foreground-muted transition-colors hover:bg-surface-200 hover:text-foreground focus-ring"
|
||||
aria-label={copied ? `${label} copied` : `Copy ${label}`}
|
||||
title={copied ? 'Copied' : 'Copy to clipboard'}
|
||||
>
|
||||
|
||||
@@ -55,7 +55,7 @@ export const tabsListVariants = cva(cn('flex'), {
|
||||
|
||||
export const tabsTriggerListVariants = cva(
|
||||
cn(
|
||||
'relative cursor-pointer flex items-center space-x-2 text-center transition focus:outline-hidden focus-visible:ring-3 focus-visible:ring-foreground-muted focus-visible:border-foreground-muted'
|
||||
'relative cursor-pointer flex items-center space-x-2 text-center transition-colors focus-ring'
|
||||
),
|
||||
{
|
||||
variants: {
|
||||
|
||||
+2
-4
@@ -260,10 +260,8 @@ export const CreateOrUpdateCustomProviderSheet = ({
|
||||
<div className="flex flex-row gap-3 items-center">
|
||||
<SheetClose
|
||||
className={cn(
|
||||
'text-muted hover:text ring-offset-background transition-opacity hover:opacity-100',
|
||||
'focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
||||
'disabled:pointer-events-none data-[state=open]:bg-secondary',
|
||||
'transition'
|
||||
'text-muted hover:text hover:opacity-100 focus-ring',
|
||||
'disabled:pointer-events-none data-[state=open]:bg-secondary'
|
||||
)}
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
|
||||
@@ -263,10 +263,8 @@ export const CreateOrUpdateOAuthAppSheet = ({
|
||||
<div className="flex flex-row gap-3 items-center">
|
||||
<SheetClose
|
||||
className={cn(
|
||||
'text-muted hover:text ring-offset-background transition-opacity hover:opacity-100',
|
||||
'focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
||||
'disabled:pointer-events-none data-[state=open]:bg-secondary',
|
||||
'transition'
|
||||
'text-muted hover:text hover:opacity-100 focus-ring',
|
||||
'disabled:pointer-events-none data-[state=open]:bg-secondary'
|
||||
)}
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
|
||||
+2
-3
@@ -30,9 +30,8 @@ export const PolicyEditorPanelHeader = ({
|
||||
<div className="flex flex-row gap-3 max-w-[75%] items-start">
|
||||
<SheetClose
|
||||
className={cn(
|
||||
'text-muted hover:text ring-offset-background transition-opacity hover:opacity-100',
|
||||
'focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
||||
'transition disabled:pointer-events-none data-[state=open]:bg-secondary',
|
||||
'text-muted hover:text hover:opacity-100 focus-ring',
|
||||
'disabled:pointer-events-none data-[state=open]:bg-secondary',
|
||||
'mt-1.5'
|
||||
)}
|
||||
>
|
||||
|
||||
+1
-1
@@ -135,7 +135,7 @@ export const SlotLagMetricsList = ({
|
||||
type="button"
|
||||
tabIndex={0}
|
||||
aria-label={`What is ${field.label}`}
|
||||
className="inline-flex h-4 w-4 items-center justify-center rounded-full bg-surface-200 text-foreground-lighter transition-colors hover:bg-surface-300 hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-foreground-lighter"
|
||||
className="inline-flex h-4 w-4 items-center justify-center rounded-full bg-surface-200 text-foreground-lighter transition-colors hover:bg-surface-300 hover:text-foreground focus-ring"
|
||||
>
|
||||
<Info size={12} />
|
||||
</button>
|
||||
|
||||
+1
-1
@@ -66,7 +66,7 @@ export const SlotStatusLegend = () => {
|
||||
type="button"
|
||||
tabIndex={0}
|
||||
aria-label="What do the slot statuses mean?"
|
||||
className="inline-flex h-4 w-4 items-center justify-center rounded-full bg-surface-200 text-foreground-lighter transition-colors hover:bg-surface-300 hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-foreground-lighter"
|
||||
className="inline-flex h-4 w-4 items-center justify-center rounded-full bg-surface-200 text-foreground-lighter transition-colors hover:bg-surface-300 hover:text-foreground focus-ring"
|
||||
>
|
||||
<Info size={12} />
|
||||
</button>
|
||||
|
||||
@@ -18,7 +18,7 @@ export const MarketplaceCard = ({ integration, isInstalled }: MarketplaceCardPro
|
||||
return (
|
||||
<Link
|
||||
href={`/project/${ref}/integrations/${integration.id}/overview`}
|
||||
className="rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground-lighter focus-visible:ring-offset-1 focus-visible:ring-offset-background"
|
||||
className="rounded-md focus-ring"
|
||||
>
|
||||
<Card className="flex min-h-[168px] h-full flex-col gap-2.5 hover:border-stronger p-4">
|
||||
<div className="flex items-start justify-between">
|
||||
|
||||
+2
-2
@@ -113,7 +113,7 @@ export const MarketplaceFeaturedHeroGrid = ({
|
||||
<div className="col-span-1 @xl:col-span-2">
|
||||
<Link
|
||||
href={`/project/${ref}/integrations/${primaryIntegration.id}/overview`}
|
||||
className="block h-full rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground-lighter focus-visible:ring-offset-1 focus-visible:ring-offset-background"
|
||||
className="block h-full rounded-md focus-ring"
|
||||
>
|
||||
<Card className="relative flex flex-row overflow-hidden h-full min-h-[168px] hover:border-stronger">
|
||||
<div className="relative z-10 flex flex-col gap-2.5 p-4 flex-1 min-w-0">
|
||||
@@ -170,7 +170,7 @@ export const MarketplaceFeaturedHeroGrid = ({
|
||||
<div key={integration.id} className="col-span-1">
|
||||
<Link
|
||||
href={`/project/${ref}/integrations/${integration.id}/overview`}
|
||||
className="block h-full rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground-lighter focus-visible:ring-offset-1 focus-visible:ring-offset-background"
|
||||
className="block h-full rounded-md focus-ring"
|
||||
>
|
||||
<Card className="flex flex-col overflow-hidden h-full hover:border-stronger">
|
||||
<div className="hidden @xl:block relative w-full h-28 bg-black/90 dark:bg-black/50 shrink-0">
|
||||
|
||||
@@ -220,7 +220,7 @@ export const MarketplaceFilterBar = ({
|
||||
onClick={() => onViewModeChange('grid')}
|
||||
className={cn(
|
||||
'border-r px-2 py-1.5 rounded-l-md cursor-pointer',
|
||||
'focus-visible:z-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground-muted focus-visible:ring-offset-1 focus-visible:ring-offset-background',
|
||||
'focus-visible:z-10 focus-ring',
|
||||
viewMode === 'grid'
|
||||
? 'bg-surface-200 text-foreground'
|
||||
: 'text-foreground-light hover:bg-surface-100'
|
||||
@@ -235,7 +235,7 @@ export const MarketplaceFilterBar = ({
|
||||
onClick={() => onViewModeChange('list')}
|
||||
className={cn(
|
||||
'px-2 py-1.5 rounded-r-md cursor-pointer',
|
||||
'focus-visible:z-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground-muted focus-visible:ring-offset-1 focus-visible:ring-offset-background',
|
||||
'focus-visible:z-10 focus-ring',
|
||||
viewMode === 'list'
|
||||
? 'bg-surface-200 text-foreground'
|
||||
: 'text-foreground-light hover:bg-surface-100'
|
||||
|
||||
@@ -33,12 +33,9 @@ export const MarketplaceListRow = ({ integration, isInstalled }: MarketplaceList
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<Link
|
||||
href={href}
|
||||
className="block w-full hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground-lighter focus-visible:ring-offset-1 focus-visible:ring-offset-background rounded-md"
|
||||
>
|
||||
<Link href={href} className="block w-full rounded-md hover:underline focus-ring">
|
||||
<div className="flex flex-wrap items-center gap-x-1.5 gap-y-1">
|
||||
<div className="relative z-10 text-sm font-medium @lg:text-sm after:absolute after:-inset-y-2 after:left-0 after:right-[calc(100%+6px)] after:content-[''] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-foreground">
|
||||
<div className="relative z-10 text-sm font-medium @lg:text-sm after:absolute after:-inset-y-2 after:left-0 after:right-[calc(100%+6px)] after:content-['']">
|
||||
{integration.name}
|
||||
</div>
|
||||
<div className="relative z-10">
|
||||
|
||||
@@ -92,7 +92,7 @@ export const ConnectSection = () => {
|
||||
onClick={() => handleActionClick(action)}
|
||||
className={cn(
|
||||
'group flex items-center gap-3 p-4 text-left transition-colors min-h-[72px] w-full',
|
||||
'hover:bg-surface-100 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-brand',
|
||||
'hover:bg-surface-100 focus-ring',
|
||||
'xl:min-h-32 xl:flex-col xl:justify-center xl:p-6 xl:text-center',
|
||||
((action.requiresActiveProject ?? true)
|
||||
? !isActiveHealthy
|
||||
|
||||
@@ -27,8 +27,7 @@ export const SortableSection = ({ id, children }: SortableSectionProps) => {
|
||||
aria-label="Drag to reorder section"
|
||||
className={cn(
|
||||
'absolute -left-6 top-2 text-foreground-muted hover:text-foreground cursor-grab active:cursor-grabbing',
|
||||
'rounded-sm outline-hidden',
|
||||
'focus-visible:outline-solid focus-visible:outline-4 focus-visible:outline-offset-1 focus-visible:outline-border-strong'
|
||||
'rounded-sm focus-ring'
|
||||
)}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
|
||||
@@ -71,7 +71,7 @@ export const QueryPerformanceMetrics = () => {
|
||||
type="button"
|
||||
tabIndex={0}
|
||||
aria-label="How are slow queries calculated?"
|
||||
className="inline-flex h-4 w-4 items-center justify-center rounded-full bg-surface-200 text-foreground-lighter transition-colors hover:bg-surface-300 hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-foreground-lighter"
|
||||
className="inline-flex h-4 w-4 items-center justify-center rounded-full bg-surface-200 text-foreground-lighter transition-colors hover:bg-surface-300 hover:text-foreground focus-ring"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
}}
|
||||
@@ -92,7 +92,7 @@ export const QueryPerformanceMetrics = () => {
|
||||
type="button"
|
||||
tabIndex={0}
|
||||
aria-label={`What is ${card.title}?`}
|
||||
className="inline-flex h-4 w-4 items-center justify-center rounded-full bg-surface-200 text-foreground-lighter transition-colors hover:bg-surface-300 hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-foreground-lighter"
|
||||
className="inline-flex h-4 w-4 items-center justify-center rounded-full bg-surface-200 text-foreground-lighter transition-colors hover:bg-surface-300 hover:text-foreground focus-ring"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
}}
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ export const BucketsPolicies = ({
|
||||
aria-label="Toggle bucket list"
|
||||
className={cn(
|
||||
'rounded-md p-1 text-foreground-light hover:text-foreground',
|
||||
'outline-hidden focus-visible:ring-2 focus-visible:ring-foreground-muted focus-visible:ring-offset-1 focus-visible:ring-offset-background'
|
||||
'focus-ring'
|
||||
)}
|
||||
>
|
||||
<ChevronUp size={14} className={cn(!expanded && 'rotate-180', 'transition')} />
|
||||
|
||||
+2
-2
@@ -32,7 +32,7 @@ export const StoragePolicyEditorModalTitle = ({
|
||||
tabIndex={0}
|
||||
onClick={onSelectBackFromTemplates}
|
||||
className={cn(
|
||||
'cursor-pointer rounded-xs opacity-20 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-foreground-muted',
|
||||
'cursor-pointer rounded-xs opacity-20 hover:opacity-100 focus-ring disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-foreground-muted',
|
||||
'hit-area-6'
|
||||
)}
|
||||
>
|
||||
@@ -51,7 +51,7 @@ export const StoragePolicyEditorModalTitle = ({
|
||||
<DocsButton href={`${DOCS_URL}/learn/auth-deep-dive/auth-policies`} />
|
||||
<DialogPrimitive.Close
|
||||
className={cn(
|
||||
'absolute p-0.5 right-3.5 top-4.5 rounded-xs opacity-20 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-foreground-muted',
|
||||
'absolute p-0.5 right-3.5 top-4.5 rounded-xs opacity-20 hover:opacity-100 focus-ring disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-foreground-muted',
|
||||
'hit-area-6'
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -24,7 +24,7 @@ const MAX_ATTACHMENTS = 5
|
||||
|
||||
const removeAttachmentButtonClassName = cn(
|
||||
'absolute -top-1 -right-1 size-4 shrink-0 rounded-full bg-red-900 p-0 cursor-pointer',
|
||||
'outline-hidden focus-visible:ring-2 focus-visible:ring-foreground-muted focus-visible:ring-offset-1 focus-visible:ring-offset-background'
|
||||
'focus-ring'
|
||||
)
|
||||
|
||||
const RemoveAttachmentIcon = () => (
|
||||
@@ -257,9 +257,9 @@ export function AttachmentUploadDisplay({
|
||||
tabIndex={0}
|
||||
aria-label="Add attachment"
|
||||
className={cn(
|
||||
'border border-stronger opacity-50 transition hover:opacity-100',
|
||||
'border border-stronger opacity-50 hover:opacity-100',
|
||||
'group flex h-14 w-14 cursor-pointer items-center justify-center rounded-sm',
|
||||
'outline-hidden focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-foreground-muted focus-visible:ring-offset-1 focus-visible:ring-offset-background'
|
||||
'focus-visible:opacity-100 focus-ring'
|
||||
)}
|
||||
onClick={addFile}
|
||||
>
|
||||
|
||||
@@ -46,7 +46,7 @@ function SupportAssistantSuccessCardLoadingShell({ className }: { className?: st
|
||||
tabIndex={0}
|
||||
aria-label="Open assistant response"
|
||||
className={cn(
|
||||
'group cursor-pointer bg-muted/50 transition-colors hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand',
|
||||
'group cursor-pointer bg-muted/50 transition-colors hover:bg-muted/50 focus-ring',
|
||||
className
|
||||
)}
|
||||
>
|
||||
|
||||
+1
-1
@@ -179,7 +179,7 @@ export const SpreadsheetImportPreview = ({
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={0}
|
||||
className="flex items-center space-x-2 cursor-pointer focus:outline-hidden focus-visible:ring-2 focus-visible:ring-primary"
|
||||
className="flex cursor-pointer items-center space-x-2 focus-ring"
|
||||
onClick={() => onSelectExpandError(key)}
|
||||
aria-expanded={isExpanded}
|
||||
aria-controls={`${key}-panel`}
|
||||
|
||||
@@ -135,7 +135,7 @@ export function SupportAssistantSuccessCardContent({
|
||||
}
|
||||
}}
|
||||
className={cn(
|
||||
'group cursor-pointer bg-muted/50 transition-colors hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand',
|
||||
'group cursor-pointer bg-muted/50 transition-colors hover:bg-muted/50 focus-ring',
|
||||
className
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -24,13 +24,13 @@ export const TwoOptionToggle = ({
|
||||
) => `absolute top-0 z-1 text-xs inline-flex h-full items-center justify-center font-medium
|
||||
${
|
||||
isActive ? 'hover:text-foreground-light hover:text-foreground' : 'hover:text-foreground'
|
||||
} hover:text-foreground focus:z-10 focus:outline-hidden focus:border-blue-300 focus:ring-blue
|
||||
transition ease-in-out duration-150`
|
||||
} hover:text-foreground focus-visible:z-10 focus-ring`
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`relative border ${borderOverride} rounded-md h-7`}
|
||||
style={{ padding: 1, width: (width + 1) * 2 }}
|
||||
role="group"
|
||||
>
|
||||
<span
|
||||
style={{ width, translate: activeOption === options[1] ? '0px' : `${width - 2}px` }}
|
||||
@@ -42,14 +42,20 @@ export const TwoOptionToggle = ({
|
||||
/>
|
||||
{options.map((option, index: number) => {
|
||||
const isDisabled = disabledOptions.includes(option)
|
||||
const isActive = activeOption === option
|
||||
const optionButton = (
|
||||
<span
|
||||
<button
|
||||
key={`toggle_${index}`}
|
||||
type="button"
|
||||
tabIndex={0}
|
||||
aria-pressed={isActive}
|
||||
// Prefer aria-disabled so TooltipTrigger asChild still receives hover/focus
|
||||
aria-disabled={isDisabled || undefined}
|
||||
style={{ width: width + 1 }}
|
||||
className={cn(
|
||||
activeOption === option ? 'text-foreground' : 'text-foreground-light',
|
||||
isActive ? 'text-foreground' : 'text-foreground-light',
|
||||
index === 0 ? 'right-0' : 'left-0',
|
||||
buttonStyle(activeOption === option),
|
||||
buttonStyle(isActive),
|
||||
isDisabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'
|
||||
)}
|
||||
onClick={() => {
|
||||
@@ -59,13 +65,13 @@ export const TwoOptionToggle = ({
|
||||
<span
|
||||
className={cn(
|
||||
'capitalize hover:text-foreground',
|
||||
activeOption === option ? 'text-foreground' : 'text-foreground-light',
|
||||
isActive ? 'text-foreground' : 'text-foreground-light',
|
||||
isDisabled && 'hover:text-foreground-light'
|
||||
)}
|
||||
>
|
||||
{option}
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
if (!isDisabled || !disabledOptionTooltip) return optionButton
|
||||
|
||||
@@ -314,7 +314,7 @@ export default function IntegrationsContent({
|
||||
title="Grid view"
|
||||
onClick={() => setFilters({ view: 'grid' })}
|
||||
className={cn(
|
||||
'relative flex items-center justify-center w-8 h-8 transition-colors rounded-l-lg focus-visible:z-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground-lighter focus-visible:ring-offset-1 focus-visible:ring-offset-background',
|
||||
'relative flex items-center justify-center w-8 h-8 rounded-l-lg focus-visible:z-10 focus-ring',
|
||||
viewMode === 'grid'
|
||||
? 'bg-surface-300 text-foreground'
|
||||
: 'bg-surface-75 text-foreground-muted hover:text-foreground hover:bg-surface-200'
|
||||
@@ -327,7 +327,7 @@ export default function IntegrationsContent({
|
||||
title="List view"
|
||||
onClick={() => setFilters({ view: 'list' })}
|
||||
className={cn(
|
||||
'relative flex items-center justify-center w-8 h-8 transition-colors border-l border-muted rounded-r-lg focus-visible:z-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground-lighter focus-visible:ring-offset-1 focus-visible:ring-offset-background',
|
||||
'relative flex items-center justify-center w-8 h-8 border-l border-muted rounded-r-lg focus-visible:z-10 focus-ring',
|
||||
viewMode === 'list'
|
||||
? 'bg-surface-300 text-foreground'
|
||||
: 'bg-surface-75 text-foreground-muted hover:text-foreground hover:bg-surface-200'
|
||||
@@ -346,7 +346,7 @@ export default function IntegrationsContent({
|
||||
<Link
|
||||
key={p.slug}
|
||||
href={`/partners/catalog/${p.slug}`}
|
||||
className="flex flex-col justify-start items-stretch group cursor-pointer transition rounded-xl focus-visible:ring-2 focus-visible:ring-foreground-lighter outline-hidden outline-0 focus-visible:outline-4 focus-visible:outline-offset-1 focus-visible:outline-foreground-lighter"
|
||||
className="flex flex-col justify-start items-stretch group cursor-pointer rounded-xl focus-ring"
|
||||
>
|
||||
<Panel
|
||||
hasActiveOnHover
|
||||
|
||||
@@ -20,13 +20,13 @@ const TwoOptionToggle = ({
|
||||
) => `absolute top-0 z-1 text-xs inline-flex h-full items-center justify-center font-medium
|
||||
${
|
||||
isActive ? 'hover:text-foreground-light hover:text-foreground' : 'hover:text-foreground'
|
||||
} hover:text-foreground focus:z-10 focus:outline-hidden focus:border-blue-300 focus:ring-blue
|
||||
transition ease-in-out duration-150`
|
||||
} hover:text-foreground focus-visible:z-10 focus-ring`
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`relative border ${borderOverride} rounded-md h-7`}
|
||||
style={{ padding: 1, width: (width + 1) * 2 }}
|
||||
role="group"
|
||||
>
|
||||
<span
|
||||
style={{ width, translate: activeOption === options[1] ? '0px' : `${width - 2}px` }}
|
||||
@@ -36,28 +36,34 @@ const TwoOptionToggle = ({
|
||||
'transition-all ease-in-out border border-strong'
|
||||
)}
|
||||
></span>
|
||||
{options.map((option: any, index: number) => (
|
||||
<span
|
||||
key={`toggle_${index}`}
|
||||
style={{ width: width + 1 }}
|
||||
className={`
|
||||
${activeOption === option ? 'text-foreground' : 'text-foreground-light'}
|
||||
{options.map((option: any, index: number) => {
|
||||
const isActive = activeOption === option
|
||||
return (
|
||||
<button
|
||||
key={`toggle_${index}`}
|
||||
type="button"
|
||||
tabIndex={0}
|
||||
aria-pressed={isActive}
|
||||
style={{ width: width + 1 }}
|
||||
className={`
|
||||
${isActive ? 'text-foreground' : 'text-foreground-light'}
|
||||
${index === 0 ? 'right-0' : 'left-0'}
|
||||
${buttonStyle(activeOption === option)}
|
||||
${buttonStyle(isActive)}
|
||||
cursor-pointer
|
||||
`}
|
||||
onClick={() => onClickOption(option)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
'capitalize hover:text-foreground',
|
||||
activeOption === option ? 'text-foreground' : 'text-foreground-light'
|
||||
)}
|
||||
onClick={() => onClickOption(option)}
|
||||
>
|
||||
{option}
|
||||
</span>
|
||||
</span>
|
||||
))}
|
||||
<span
|
||||
className={cn(
|
||||
'capitalize hover:text-foreground',
|
||||
isActive ? 'text-foreground' : 'text-foreground-light'
|
||||
)}
|
||||
>
|
||||
{option}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ const AnnouncementBadge = ({
|
||||
hover:border-foreground-muted/30
|
||||
shadow-md
|
||||
overflow-hidden
|
||||
focus-visible:outline-hidden focus-visible:ring-brand-600 focus-visible:ring-2 focus-visible:rounded-full
|
||||
focus-ring rounded-full
|
||||
`,
|
||||
!badge && 'pl-5'
|
||||
)}
|
||||
|
||||
@@ -11,7 +11,7 @@ const Button = (props: Props) => {
|
||||
|
||||
const colorClass =
|
||||
type === 'primary'
|
||||
? 'px-3 py-2 shadow-xs border border-transparent text-white bg-brand-400 hover:bg-brand-300 focus:ring-2 focus:ring-offset-2 focus:ring-brand-300'
|
||||
? 'px-3 py-2 shadow-xs border border-transparent text-white bg-brand-400 hover:bg-brand-300'
|
||||
: 'text-brand-400 bg-none'
|
||||
|
||||
const textClass = type === 'primary' ? 'font-medium left-3 group-hover:left-0' : 'font-normal'
|
||||
@@ -27,7 +27,7 @@ const Button = (props: Props) => {
|
||||
|
||||
const sharedClassName = `
|
||||
group inline-flex items-center rounded-md text-sm
|
||||
leading-4 transition focus:outline-hidden ${colorClass} ${className}
|
||||
leading-4 focus-ring ${colorClass} ${className}
|
||||
`
|
||||
|
||||
const content = (
|
||||
|
||||
@@ -38,8 +38,8 @@ export function LabelBadges({
|
||||
href={changelogTagFilterUrl(label.name)}
|
||||
className={
|
||||
tiny
|
||||
? 'inline-flex shrink-0 no-underline focus-visible:ring-brand-default rounded-sm focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden'
|
||||
: 'inline-flex shrink-0 no-underline focus-visible:ring-brand-default rounded-md focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden'
|
||||
? 'inline-flex shrink-0 no-underline focus-ring rounded-sm'
|
||||
: 'inline-flex shrink-0 no-underline focus-ring rounded-md'
|
||||
}
|
||||
onClick={onBadgeClick}
|
||||
>
|
||||
|
||||
@@ -170,7 +170,7 @@ export function FeaturesMatrix({ features }: FeaturesMatrixProps) {
|
||||
<TableCell className="px-3 py-3 min-w-0 hover:bg-transparent!">
|
||||
<Link
|
||||
href={`/features/${feature.slug}`}
|
||||
className="flex items-center gap-2 text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground-lighter rounded min-w-0"
|
||||
className="flex items-center gap-2 text-foreground focus-ring rounded min-w-0"
|
||||
>
|
||||
<div className="shrink-0 w-7 h-7 rounded-md bg-surface-200 border border-muted hidden md:flex items-center justify-center">
|
||||
<feature.icon className="w-3.5 h-3.5 text-foreground-light group-hover/row:text-foreground transition-colors" />
|
||||
|
||||
@@ -23,7 +23,7 @@ function ModulesNav({ activePage, docsUrl }: Props) {
|
||||
key={currentModule.name}
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 px-2 first:-ml-2 py-3 border-b border-transparent text-sm text-foreground-lighter hover:text-foreground',
|
||||
'focus-visible:ring-2 focus-visible:ring-foreground-lighter focus-visible:text-foreground focus-visible:outline-brand-600',
|
||||
'focus-ring focus-visible:text-foreground',
|
||||
currentModule.name === activePage && 'border-foreground-light text-foreground'
|
||||
)}
|
||||
href={currentModule.url ?? ''}
|
||||
@@ -52,7 +52,7 @@ function ModulesNav({ activePage, docsUrl }: Props) {
|
||||
<Link
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 py-3 border-b border-transparent text-sm text-foreground-lighter hover:text-foreground',
|
||||
'focus-visible:ring-2 focus-visible:ring-foreground-lighter focus-visible:text-foreground focus-visible:outline-brand-600'
|
||||
'focus-ring focus-visible:text-foreground'
|
||||
)}
|
||||
href={docsUrl}
|
||||
>
|
||||
|
||||
@@ -27,7 +27,7 @@ export const DevelopersDropdown = () => {
|
||||
<li key={link.text}>
|
||||
<Link
|
||||
href={link.url!}
|
||||
className="flex group items-center gap-2 text-foreground-light text-sm hover:text-foreground focus-visible:text-foreground focus-visible:ring-2 focus-visible:outline-hidden focus-visible:rounded-sm focus-visible:ring-foreground-lighter"
|
||||
className="flex group items-center gap-2 text-foreground-light text-sm hover:text-foreground focus-visible:text-foreground focus-ring rounded-sm"
|
||||
>
|
||||
{Icon && <Icon size={16} strokeWidth={1.3} />}
|
||||
<span>{link.text}</span>
|
||||
@@ -51,7 +51,7 @@ export const DevelopersDropdown = () => {
|
||||
<div className="flex-col gap-2 py-8 px-10">
|
||||
<Link
|
||||
href="/blog"
|
||||
className="group flex items-center gap-1 text-foreground-lighter hover:text-foreground text-xs uppercase tracking-widest font-mono mb-5 focus-visible:ring-2 focus-visible:outline-hidden focus-visible:ring-foreground-lighter focus-visible:ring-offset-4 focus-visible:ring-offset-background-alternative focus-visible:rounded-xs focus-visible:text-foreground"
|
||||
className="group flex items-center gap-1 text-foreground-lighter hover:text-foreground text-xs uppercase tracking-widest font-mono mb-5 focus-ring focus-visible:ring-offset-4 focus-visible:ring-offset-background-alternative rounded-xs focus-visible:text-foreground"
|
||||
>
|
||||
<span>Blog</span>
|
||||
<ChevronRight className="h-3 w-3 transition-transform will-change-transform -translate-x-1 group-hover:translate-x-0" />
|
||||
@@ -61,7 +61,7 @@ export const DevelopersDropdown = () => {
|
||||
<li key={post.title}>
|
||||
<Link
|
||||
href={post.url}
|
||||
className="group flex flex-col focus-visible:ring-2 focus-visible:outline-hidden focus-visible:ring-foreground-lighter focus-visible:ring-offset-4 focus-visible:ring-offset-background-alternative focus-visible:rounded-sm"
|
||||
className="group flex flex-col focus-ring focus-visible:ring-offset-4 focus-visible:ring-offset-background-alternative rounded-sm"
|
||||
>
|
||||
<p className="text-foreground-light mb-0 line-clamp-2 group-hover:text-foreground group-focus-visible:text-foreground">
|
||||
{post.title}
|
||||
|
||||
@@ -11,7 +11,7 @@ const HamburgerButton = (props: HamburgerButtonProps) => (
|
||||
<button
|
||||
tabIndex={0}
|
||||
className={cn(
|
||||
'text-foreground-lighter focus:ring-brand bg-transparent hover:text-foreground-light transition-colors hover:bg-overlay inline-flex items-center justify-center rounded-md p-2 focus:outline-hidden focus:ring-2 focus:ring-inset'
|
||||
'text-foreground-lighter bg-transparent hover:text-foreground-light hover:bg-overlay inline-flex items-center justify-center rounded-md p-2 focus-ring'
|
||||
)}
|
||||
aria-expanded="false"
|
||||
>
|
||||
|
||||
@@ -31,7 +31,7 @@ const MenuItem = React.forwardRef<
|
||||
href={href}
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'group/menu-item flex items-center text-foreground-light text-sm hover:text-foreground select-none gap-3 rounded-md p-2 leading-none no-underline outline-hidden focus-visible:ring-2 focus-visible:ring-foreground-lighter focus-visible:text-foreground',
|
||||
'group/menu-item flex items-center text-foreground-light text-sm hover:text-foreground select-none gap-3 rounded-md p-2 leading-none no-underline focus-ring focus-visible:text-foreground',
|
||||
description && 'items-center',
|
||||
className
|
||||
)}
|
||||
|
||||
@@ -90,8 +90,7 @@ export const MobileMenu = ({ open, setOpen, menu }: Props) => {
|
||||
rounded-lg border
|
||||
bg-alternative-200 text-foreground-light
|
||||
hover:text-foreground hover:border-foreground-muted
|
||||
focus-visible:text-foreground focus-visible:ring-2 focus-visible:outline-hidden
|
||||
focus-visible:rounded-sm focus-visible:ring-foreground-lighter
|
||||
focus-visible:text-foreground focus-ring
|
||||
"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
@@ -194,10 +193,7 @@ export const MobileMenu = ({ open, setOpen, menu }: Props) => {
|
||||
) : (
|
||||
<Link
|
||||
href={menuItem.url ?? '/'}
|
||||
className={cn(
|
||||
className,
|
||||
'block focus-visible:ring-2 focus-visible:outline-hidden focus-visible:ring-foreground-lighter focus-visible:rounded-sm'
|
||||
)}
|
||||
className={cn(className, 'block focus-ring rounded-sm')}
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
{menuItem.title}
|
||||
@@ -224,7 +220,7 @@ export const MobileMenu = ({ open, setOpen, menu }: Props) => {
|
||||
<Link
|
||||
href="/"
|
||||
as="/"
|
||||
className="block w-auto h-6 focus-visible:ring-2 focus-visible:outline-hidden focus-visible:ring-foreground-lighter focus-visible:ring-offset-4 focus-visible:ring-offset-background-alternative focus-visible:rounded-xs"
|
||||
className="block w-auto h-6 focus-ring focus-visible:ring-offset-4 focus-visible:ring-offset-background-alternative rounded-xs"
|
||||
>
|
||||
<SupabaseWordmark />
|
||||
</Link>
|
||||
@@ -232,7 +228,7 @@ export const MobileMenu = ({ open, setOpen, menu }: Props) => {
|
||||
tabIndex={0}
|
||||
onClick={() => setOpen(false)}
|
||||
type="button"
|
||||
className="inline-flex items-center justify-center p-2 rounded-md text-foreground-lighter focus:ring-brand hover:text-foreground-light transition-colors focus:outline-hidden focus:ring-2 focus:ring-inset"
|
||||
className="inline-flex items-center justify-center p-2 rounded-md text-foreground-lighter hover:text-foreground-light focus-ring"
|
||||
>
|
||||
<span className="sr-only">Close menu</span>
|
||||
<svg
|
||||
|
||||
@@ -51,8 +51,8 @@ export const ProductDropdown = () => {
|
||||
flex items-start gap-2
|
||||
text-xs leading-none
|
||||
text-foreground-light hover:text-foreground
|
||||
no-underline outline-hidden select-none
|
||||
focus-visible:ring-2 focus-visible:ring-foreground-lighter focus-visible:text-foreground
|
||||
no-underline select-none
|
||||
focus-ring focus-visible:text-foreground
|
||||
"
|
||||
>
|
||||
<div className="w-8 h-8 min-w-8 shrink-0 bg-background border flex items-center justify-center rounded-md">
|
||||
@@ -98,8 +98,8 @@ export const ProductDropdown = () => {
|
||||
flex items-start gap-2
|
||||
text-xs leading-none
|
||||
text-foreground-light hover:text-foreground
|
||||
no-underline outline-hidden select-none
|
||||
focus-visible:ring-2 focus-visible:ring-foreground-lighter focus-visible:text-foreground
|
||||
no-underline select-none
|
||||
focus-ring focus-visible:text-foreground
|
||||
"
|
||||
>
|
||||
<div className="w-8 h-8 min-w-8 shrink-0 bg-background border flex items-center justify-center rounded-md">
|
||||
@@ -127,7 +127,7 @@ export const ProductDropdown = () => {
|
||||
<div>
|
||||
<Link
|
||||
href="/customers"
|
||||
className="group flex items-center gap-1 text-foreground-lighter hover:text-foreground text-xs uppercase tracking-widest font-mono mb-4 focus-visible:ring-2 focus-visible:outline-hidden focus-visible:ring-foreground-lighter focus-visible:ring-offset-4 focus-visible:ring-offset-background-alternative focus-visible:rounded-xs focus-visible:text-foreground"
|
||||
className="group flex items-center gap-1 text-foreground-lighter hover:text-foreground text-xs uppercase tracking-widest font-mono mb-4 focus-ring focus-visible:ring-offset-4 focus-visible:ring-offset-background-alternative rounded-xs focus-visible:text-foreground"
|
||||
>
|
||||
Customer Stories
|
||||
<ChevronRight className="h-3 w-3 transition-transform will-change-transform -translate-x-1 group-hover:translate-x-0" />
|
||||
@@ -137,7 +137,7 @@ export const ProductDropdown = () => {
|
||||
<li key={customer.organization}>
|
||||
<Link
|
||||
href={customer.url}
|
||||
className="group flex items-center gap-3 focus-visible:ring-2 focus-visible:outline-hidden focus-visible:ring-foreground-lighter focus-visible:ring-offset-4 focus-visible:ring-offset-background-alternative focus-visible:rounded-sm"
|
||||
className="group flex items-center gap-3 focus-ring focus-visible:ring-offset-4 focus-visible:ring-offset-background-alternative rounded-sm"
|
||||
>
|
||||
<div className="relative rounded-md bg-background border group-hover:border-foreground-muted/50 h-14 w-28 xl:h-14 xl:w-20 shrink-0 overflow-auto">
|
||||
<Image
|
||||
|
||||
@@ -93,7 +93,7 @@ const RightClickBrandLogo = () => {
|
||||
onContextMenu={handleRightClick}
|
||||
onFocus={handleKeyboardOpen}
|
||||
onKeyDown={(e) => e.key === 'Enter' && router?.push('/')}
|
||||
className="block w-auto h-6 focus-visible:ring-2 focus-visible:outline-hidden focus-visible:ring-foreground-lighter focus-visible:ring-offset-4 focus-visible:ring-offset-background-alternative focus-visible:rounded-xs"
|
||||
className="block w-auto h-6 focus-ring focus-visible:ring-offset-4 focus-visible:ring-offset-background-alternative rounded-xs"
|
||||
>
|
||||
<SupabaseWordmark />
|
||||
</Link>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { motion, AnimatePresence } from 'framer-motion'
|
||||
import { navData as DevelopersData } from 'data/Solutions'
|
||||
import { AnimatePresence, motion } from 'framer-motion'
|
||||
import { ArrowLeftRight, ChevronRight } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useState } from 'react'
|
||||
import { useWindowSize } from 'react-use'
|
||||
|
||||
import { navData as DevelopersData } from 'data/Solutions'
|
||||
|
||||
type LinkProps = {
|
||||
text: string
|
||||
description?: string
|
||||
@@ -50,7 +49,7 @@ const LinksGroup = ({ links, label }: { links: LinkProps[]; label: string }) =>
|
||||
<li key={link.text}>
|
||||
<Link
|
||||
href={link.url!}
|
||||
className="flex group items-center gap-2 text-foreground-light text-sm hover:text-foreground focus-visible:text-foreground focus-visible:ring-2 focus-visible:outline-hidden focus-visible:rounded-sm focus-visible:ring-foreground-lighter"
|
||||
className="flex group items-center gap-2 text-foreground-light text-sm hover:text-foreground focus-visible:text-foreground focus-ring rounded-sm"
|
||||
>
|
||||
{Icon && <Icon size={16} strokeWidth={1.3} />}
|
||||
<span>{link.text}</span>
|
||||
|
||||
@@ -130,7 +130,7 @@ const Nav = ({ hideNavbar, stickyNavbar = true }: Props) => {
|
||||
<NavigationMenuTrigger
|
||||
className={cn(
|
||||
buttonVariants({ variant: 'text', size: 'small' }),
|
||||
'bg-transparent! hover:text-brand-link data-open:text-brand-link! data-radix-collection-item:focus-visible:ring-2 data-radix-collection-item:focus-visible:ring-foreground-lighter data-radix-collection-item:focus-visible:text-foreground px-2 h-auto'
|
||||
'bg-transparent! hover:text-brand-link data-open:text-brand-link! focus-ring focus-visible:text-foreground px-2 h-auto'
|
||||
)}
|
||||
>
|
||||
{menuItem.title}
|
||||
|
||||
@@ -189,15 +189,18 @@ const PricingComputeSection = () => {
|
||||
<button
|
||||
tabIndex={0}
|
||||
onClick={() => setShowTable(!showTable)}
|
||||
className="w-full p-2 border-t border-muted text-foreground focus-visible:outline-brand-600 focus-visible:rounded-b-xl text-sm bg-alternative flex items-center justify-center gap-2"
|
||||
className="group w-full p-2 border-t border-muted text-foreground outline-hidden rounded-b-xl text-sm bg-alternative flex items-center justify-center"
|
||||
>
|
||||
<ChevronDownIcon
|
||||
className={cn(
|
||||
'w-4 transition-transform transform origin-center',
|
||||
showTable ? 'rotate-180' : 'rotate-0'
|
||||
)}
|
||||
/>{' '}
|
||||
{!showTable ? 'Expand' : 'Hide'} Pricing breakdown
|
||||
{/* Label-scoped ring: full-bleed focus-ring clips under Panel overflow-hidden */}
|
||||
<span className="inline-flex items-center gap-2 rounded-md px-2 py-0.5 group-focus-visible:ring-2 group-focus-visible:ring-ring">
|
||||
<ChevronDownIcon
|
||||
className={cn(
|
||||
'w-4 transition-transform transform origin-center',
|
||||
showTable ? 'rotate-180' : 'rotate-0'
|
||||
)}
|
||||
/>
|
||||
{!showTable ? 'Expand' : 'Hide'} Pricing breakdown
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
@@ -30,7 +30,7 @@ const ProductCard = ({
|
||||
<Link
|
||||
href={url}
|
||||
className={cn(
|
||||
'group relative w-full sm:h-[400px] flex flex-col gap-5 lg:flex-row focus:outline-hidden focus:border-none focus:ring-brand-600 focus:ring-2 focus:rounded-xl',
|
||||
'group relative w-full sm:h-[400px] flex flex-col gap-5 lg:flex-row focus-ring rounded-xl',
|
||||
className
|
||||
)}
|
||||
onClick={onClick}
|
||||
|
||||
@@ -21,7 +21,7 @@ function ProductsNav({ activePage }: Props) {
|
||||
key={product.name}
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 px-2 first:-ml-2 py-4 border-b border-transparent text-sm text-foreground-lighter hover:text-foreground',
|
||||
'focus-visible:ring-2 focus-visible:ring-foreground-lighter focus-visible:text-foreground focus-visible:outline-brand-600',
|
||||
'focus-ring focus-visible:text-foreground',
|
||||
product.name === activePage && 'border-foreground-light text-foreground'
|
||||
)}
|
||||
href={`/${isAuth ? 'auth' : product.name.toLowerCase().replace(' ', '-')}`}
|
||||
|
||||
@@ -88,7 +88,7 @@ function SolutionsStickyNav({ type, activeItem, className }: Props) {
|
||||
key={item.name}
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 px-2 first:-ml-2 py-4 border-b border-transparent text-sm text-foreground-lighter hover:text-foreground',
|
||||
'focus-visible:ring-2 focus-visible:ring-foreground-lighter focus-visible:text-foreground focus-visible:outline-brand-600',
|
||||
'focus-ring focus-visible:text-foreground',
|
||||
isActive && 'border-foreground-light text-foreground'
|
||||
)}
|
||||
href={item.href}
|
||||
|
||||
@@ -384,7 +384,7 @@ function ChangelogIndex({ featured, restIndex, allIndex }: PageProps) {
|
||||
<a
|
||||
key={`${entry.number}-${label.name}`}
|
||||
href={changelogTagFilterUrl(label.name)}
|
||||
className="group inline-flex no-underline focus-visible:ring-brand-default rounded-md focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden"
|
||||
className="group inline-flex no-underline focus-ring rounded-md"
|
||||
>
|
||||
<Badge className="group-hover:text-foreground-light text-foreground-lighter group-hover:border-foreground-muted px-1.5 py-px text-[11px] tracking-normal lowercase">
|
||||
{changelogLabelDisplayName(label.name)}
|
||||
|
||||
@@ -220,7 +220,7 @@ const Press = () => {
|
||||
href={x.href}
|
||||
key={x.href}
|
||||
target="_blank"
|
||||
className="flex flex-col justify-start items-stretch group cursor-pointer transition rounded-xl focus-visible:ring-2 focus-visible:ring-foreground-lighter outline-hidden outline-0 focus-visible:outline-4 focus-visible:outline-offset-1 focus-visible:outline-foreground-lighter"
|
||||
className="flex flex-col justify-start items-stretch group cursor-pointer rounded-xl focus-ring"
|
||||
>
|
||||
<Panel
|
||||
hasActiveOnHover
|
||||
@@ -247,7 +247,7 @@ const Press = () => {
|
||||
href={x.href}
|
||||
key={x.href}
|
||||
target="_blank"
|
||||
className="flex flex-col justify-start items-stretch group cursor-pointer transition rounded-xl focus-visible:ring-2 focus-visible:ring-foreground-lighter outline-hidden outline-0 focus-visible:outline-4 focus-visible:outline-offset-1 focus-visible:outline-foreground-lighter"
|
||||
className="flex flex-col justify-start items-stretch group cursor-pointer rounded-xl focus-ring"
|
||||
>
|
||||
<Panel
|
||||
hasActiveOnHover
|
||||
|
||||
@@ -280,7 +280,7 @@ function FeaturesPage() {
|
||||
title="Grid view"
|
||||
onClick={() => setViewMode('grid')}
|
||||
className={cn(
|
||||
'relative flex items-center justify-center w-8 h-8 transition-colors rounded-l-lg focus-visible:z-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground-lighter focus-visible:ring-offset-1 focus-visible:ring-offset-background',
|
||||
'relative flex items-center justify-center w-8 h-8 rounded-l-lg focus-visible:z-10 focus-ring',
|
||||
viewMode === 'grid'
|
||||
? 'bg-surface-300 text-foreground'
|
||||
: 'bg-surface-75 text-foreground-muted hover:text-foreground hover:bg-surface-200'
|
||||
@@ -293,7 +293,7 @@ function FeaturesPage() {
|
||||
title="Matrix view"
|
||||
onClick={() => setViewMode('matrix')}
|
||||
className={cn(
|
||||
'relative flex items-center justify-center w-8 h-8 transition-colors border-l border-muted rounded-r-lg focus-visible:z-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground-lighter focus-visible:ring-offset-1 focus-visible:ring-offset-background',
|
||||
'relative flex items-center justify-center w-8 h-8 border-l border-muted rounded-r-lg focus-visible:z-10 focus-ring',
|
||||
viewMode === 'matrix'
|
||||
? 'bg-surface-300 text-foreground'
|
||||
: 'bg-surface-75 text-foreground-muted hover:text-foreground hover:bg-surface-200'
|
||||
@@ -316,7 +316,7 @@ function FeaturesPage() {
|
||||
<Link
|
||||
key={`feat-${feature.title}`}
|
||||
href={`/features/${feature.slug}`}
|
||||
className="flex flex-col justify-start items-stretch group cursor-pointer transition rounded-xl focus-visible:ring-2 focus-visible:ring-foreground-lighter outline-hidden outline-0 focus-visible:outline-4 focus-visible:outline-offset-1 focus-visible:outline-foreground-lighter"
|
||||
className="flex flex-col justify-start items-stretch group cursor-pointer rounded-xl focus-ring"
|
||||
>
|
||||
<Panel
|
||||
hasActiveOnHover
|
||||
|
||||
@@ -29,7 +29,7 @@ export function TextLink({
|
||||
<Link
|
||||
href={url}
|
||||
className={cn(
|
||||
'group/text-link text-foreground-light hover:text-foreground mt-3 block cursor-pointer text-sm focus-visible:ring-2 focus-visible:outline-hidden focus-visible:rounded-xs focus-visible:ring-foreground-lighter focus-visible:text-foreground',
|
||||
'group/text-link text-foreground-light hover:text-foreground mt-3 block cursor-pointer text-sm focus-ring focus-visible:rounded-xs focus-visible:text-foreground',
|
||||
className
|
||||
)}
|
||||
target={target}
|
||||
|
||||
@@ -293,8 +293,8 @@ const MultiSelectorTrigger = React.forwardRef<HTMLButtonElement, MultiSelectorTr
|
||||
className={cn(
|
||||
'flex w-full min-w-[200px] min-h-[40px] items-center justify-between rounded-md border',
|
||||
'border-alternative bg-control px-3 py-2 text-sm',
|
||||
'ring-offset-background placeholder:text-muted-foreground',
|
||||
'focus:outline-hidden focus:ring-2 focus:ring-background-control focus:ring-offset-2 focus-visible:ring-offset-foreground-muted',
|
||||
'placeholder:text-muted-foreground',
|
||||
'focus-ring',
|
||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'hover:border-primary transition-colors duration-200',
|
||||
className
|
||||
|
||||
@@ -48,14 +48,14 @@ const RadioGroupStackedItem = React.forwardRef<
|
||||
// Enabled/hover states
|
||||
'enabled:cursor-pointer enabled:hover:bg-surface-300 enabled:hover:border-foreground-muted',
|
||||
// Focus state
|
||||
'focus:ring-background-control focus:border-control focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-background-control focus-visible:ring-offset-2 focus-visible:ring-offset-foreground-muted',
|
||||
'focus-ring enabled:focus-visible:border-control',
|
||||
// Z-index for interactions
|
||||
'hover:z-1 focus-visible:z-1 data-[state=checked]:z-1',
|
||||
// Checked state
|
||||
'data-[state=checked]:ring-1 data-[state=checked]:ring-border',
|
||||
'data-[state=checked]:bg-surface-300 data-[state=checked]:border-foreground-muted',
|
||||
// Transitions and group
|
||||
'transition group',
|
||||
// Colors only — avoid bare `transition` so the focus ring does not animate
|
||||
'transition-colors group',
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
@@ -66,12 +66,11 @@ const RadioGroupStackedItem = React.forwardRef<
|
||||
// Base styles
|
||||
'aspect-square h-4 w-4 min-w-4 min-h-4 rounded-full border relative',
|
||||
'flex items-center justify-center',
|
||||
'ring-offset-background transition',
|
||||
// States
|
||||
'group-data-[state=checked]:border-foreground-muted',
|
||||
'group-focus:border-foreground-muted group-focus:outline-hidden',
|
||||
'group-focus-visible:ring-2 group-focus-visible:ring-background-control group-focus-visible:ring-offset-2 group-focus-visible:ring-offset-foreground-muted',
|
||||
'group-hover:border-foreground-muted'
|
||||
'group-focus-visible:border-foreground-muted',
|
||||
'group-focus-visible:ring-2 group-focus-visible:ring-ring group-focus-visible:ring-offset-2 group-focus-visible:ring-offset-background',
|
||||
'group-hover:border-foreground-muted transition-colors'
|
||||
)}
|
||||
>
|
||||
<RadioGroupPrimitive.Indicator className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
|
||||
|
||||
@@ -114,7 +114,7 @@ const DialogContent = React.forwardRef<
|
||||
{!hideClose && (
|
||||
<DialogPrimitive.Close
|
||||
className={cn(
|
||||
'absolute p-0.5 right-3.5 top-3.5 rounded-xs opacity-20 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-foreground-muted',
|
||||
'absolute p-0.5 right-3.5 top-3.5 rounded-xs opacity-20 transition-opacity hover:opacity-100 focus-ring disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-foreground-muted',
|
||||
'hit-area-6'
|
||||
)}
|
||||
>
|
||||
@@ -197,7 +197,7 @@ const DialogClose = React.forwardRef<
|
||||
<DialogPrimitive.Close
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-foreground-muted',
|
||||
'opacity-70 transition-opacity hover:opacity-100 focus-ring disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-foreground-muted',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -180,7 +180,7 @@ const SheetContent = React.forwardRef<
|
||||
{showClose ? (
|
||||
<SheetPrimitive.Close
|
||||
className={cn(
|
||||
'absolute right-4 top-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary',
|
||||
'absolute right-4 top-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus-ring disabled:pointer-events-none data-[state=open]:bg-secondary',
|
||||
'hit-area-6'
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -54,10 +54,10 @@ const SonnerToaster = ({ toastOptions, ...props }: ToasterProps) => {
|
||||
'group toast group-[.toaster]:!bg-destructive-200 group-[.toaster]:!border-destructive-500',
|
||||
closeButton: cn(
|
||||
// unset all styles set from sonner
|
||||
'absolute right-2 top-2 size-6 flex items-center justify-center rounded-md text-foreground-light opacity-0 transition',
|
||||
'hover:text-foreground hover:bg-surface-200 focus:opacity-100 focus:outline-hidden focus:ring-2 group-hover:opacity-100',
|
||||
'absolute right-2 top-2 size-6 flex items-center justify-center rounded-md text-foreground-light opacity-0 transition-opacity',
|
||||
'hover:text-foreground hover:bg-surface-200 focus-visible:opacity-100 focus-ring group-hover:opacity-100',
|
||||
'group-[.destructive]:text-destructive-300 group-[.destructive]:hover:text-destructive-50',
|
||||
'group-[.destructive]:focus:ring-destructive-400 group-[.destructive]:focus:ring-offset-destructive-600',
|
||||
'group-[.destructive]:focus-visible:ring-destructive-400 group-[.destructive]:focus-visible:ring-offset-destructive-600',
|
||||
'left-auto transform-none border-0 border-transparent'
|
||||
),
|
||||
content: 'grow',
|
||||
|
||||
Reference in new issue
Block a user