mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
## What kind of change does this PR introduce? Accessibility / lint hardening (Safari keyboard focus). ## What is the current behavior? `supabase/require-explicit-tabindex` is `'warn'`. Studio’s ratchet was at 0 but the rule was still ratcheted; www / docs / design-system still had raw `<button>` / `role="button"` call sites without an explicit `tabIndex`. [DEPR-627](https://linear.app/supabase/issue/DEPR-627) · follow-up to #47984 / #48040 ## What is the new behavior? - Shared config: `'supabase/require-explicit-tabindex': 'error'` - Swept www / docs / design-system (+ Studio test fixtures the ratchet skipped) - Removed the rule from the Studio ratchet + baselines ## To test Prefer **Safari**. This PR only adds explicit `tabIndex` to raw `<button>` / `role="button"` call sites — not links, and not controls that already go through `Button` from `ui`. ### Marketing (`www`) ([staging link](https://zone-www-dot-com-git-danny-depr-627-promote-req-7ae43c-supabase.vercel.app/)) - [x] Homepage frameworks / dashboard feature tabs — Tab through each tab button - [x] Product pages (e.g. `/auth`, `/database`) — section tab switchers - [x] Narrow viewport — open the hamburger; Tab through menu buttons - [x] `/partners/catalog` — filter / view controls - [x] Blog view toggle (list ↔ grid) ### Docs ([staging link](https://docs-git-danny-depr-627-promote-require-explici-25e46d-supabase.vercel.app/)) - [x] **Desktop (≥ lg):** top-right **⋯ menu** (hamburger icon) — opens a dropdown that includes Theme. Not a separate theme button. - [x] **Mobile (< lg):** top-right **hamburger** opens the sheet; close (X) is the raw button we tagged. Theme inside the sheet uses `ThemeToggle` / `DropdownMenuTrigger` from `ui` (already supposed to set `tabIndex`). - [x] **Code blocks** — copy / language controls - [x] **Is this helpful?** — X / check are `Button` from `ui` (should already Tab). After voting **while signed in**, the follow-up “What went well?” / “How can we improve?” text button is the raw one we tagged. - [x] **AI Tools → Copy as Markdown** (right rail on a guide) — this is the only GuidesSidebar control this PR changed. “On this page” TOC items are **links**, not covered by this lint. - [x] **Reference docs** (e.g. JS client reference) — section headers that expand/collapse in the left nav (`Collapsible.Trigger`) - [x] **Troubleshooting index** — type in the search field, then Tab to the **clear (X)** control ### Dashboard (`studio`) No production UI changes in this PR (tests + lint config only). Quick Safari smoke that prior tabindex work still holds: - [x] Project sidebar — Tab through primary nav links - [x] Settings → General — Tab through inputs / buttons - [x] Storage → Files — Tab a bucket row / file actions
153 lines
4.2 KiB
TypeScript
153 lines
4.2 KiB
TypeScript
import { Music } from 'lucide-react'
|
|
import { useTheme } from 'next-themes'
|
|
import Link from 'next/link'
|
|
import { useEffect, useState } from 'react'
|
|
import { cn, IconDocumentation, IconMicSolid, IconProductHunt, IconYoutubeSolid } from 'ui'
|
|
import { ExpandableVideo } from 'ui-patterns/ExpandableVideo'
|
|
|
|
import { StepLink } from './data/lw15_data'
|
|
|
|
export const LWSVG = (props: React.SVGProps<SVGSVGElement>) => {
|
|
return (
|
|
<svg
|
|
width="270"
|
|
height="146"
|
|
viewBox="0 0 270 146"
|
|
className="h-full w-auto"
|
|
fill="none"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
{...props}
|
|
>
|
|
<path d="M21.2516 0.5V127.9H97.8516V145.5H0.851562V0.5H21.2516Z" fill="currentColor" />
|
|
<path
|
|
d="M192.348 0.5L220.948 122.3L247.748 0.5H269.148L232.948 145.5H208.148L180.148 26.3L152.748 145.5H127.948L91.1484 0.5H113.548L141.548 122.3L168.348 0.5H192.348Z"
|
|
fill="currentColor"
|
|
/>
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
export const FifteenSVG = (props: React.SVGProps<SVGSVGElement>) => {
|
|
return (
|
|
<svg
|
|
width="185"
|
|
height="148"
|
|
viewBox="0 0 185 148"
|
|
fill="none"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
{...props}
|
|
>
|
|
<path
|
|
d="M52.1653 145.393H33.8891V31.6856H0.148438V19.252C20.8347 18.6504 31.8807 15.6423 39.7134 0H52.1653V145.393Z"
|
|
fill="currentColor"
|
|
/>
|
|
<path
|
|
d="M132.332 132.759C149.805 132.759 164.868 120.125 164.868 97.664C164.868 73.5989 147.998 63.1707 132.132 63.1707C117.069 63.1707 107.228 71.9946 104.014 79.2141L85.7381 78.2114L95.1775 0H178.726V17.0461H110.039L105.019 58.3577C112.851 50.9377 124.299 47.3279 136.349 47.3279C161.855 47.3279 184.148 65.3767 184.148 97.4634C184.148 130.753 159.445 148 132.332 148C98.7925 148 83.1272 126.743 80.5163 103.279H99.3951C102.408 121.729 112.048 132.759 132.332 132.759Z"
|
|
fill="currentColor"
|
|
/>
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
export const LW15ThemeSwitcher = ({
|
|
className,
|
|
...props
|
|
}: React.HTMLAttributes<HTMLDivElement>) => {
|
|
const { resolvedTheme, setTheme } = useTheme()
|
|
const isDarkMode = resolvedTheme?.includes('dark')
|
|
|
|
return (
|
|
<div className={cn('flex items-center justify-start gap-2', className)} {...props}>
|
|
<button
|
|
tabIndex={0}
|
|
onClick={() => setTheme('dark')}
|
|
className={cn(isDarkMode ? 'text-foreground' : 'text-foreground-lighter')}
|
|
>
|
|
dark mode
|
|
</button>{' '}
|
|
/
|
|
<button
|
|
tabIndex={0}
|
|
onClick={() => setTheme('light')}
|
|
className={cn(!isDarkMode ? 'text-foreground' : 'text-foreground-lighter')}
|
|
>
|
|
light mode
|
|
</button>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
interface DayLink extends StepLink {
|
|
className?: string
|
|
}
|
|
|
|
export const DayLink = ({ type, icon, text, href = '', className }: DayLink) => {
|
|
const [isMounted, setIsMounted] = useState(false)
|
|
|
|
useEffect(() => {
|
|
setIsMounted(true)
|
|
}, [])
|
|
|
|
if (!isMounted) return null
|
|
|
|
const linkTypes = {
|
|
blog: {
|
|
icon: IconDocumentation,
|
|
text: 'Blog Post',
|
|
},
|
|
docs: {
|
|
icon: IconDocumentation,
|
|
text: 'Docs',
|
|
},
|
|
productHunt: {
|
|
icon: IconProductHunt,
|
|
text: 'Product Hunt',
|
|
},
|
|
video: {
|
|
icon: IconYoutubeSolid,
|
|
text: 'Watch video',
|
|
},
|
|
podcast: {
|
|
icon: Music,
|
|
text: 'Podcast',
|
|
},
|
|
xSpace: {
|
|
icon: IconMicSolid,
|
|
text: 'X Space',
|
|
},
|
|
}
|
|
const isTargetBlank = () => {
|
|
switch (type) {
|
|
case 'productHunt':
|
|
case 'xSpace':
|
|
case 'docs':
|
|
return true
|
|
}
|
|
}
|
|
const Text = () => <>{text ?? linkTypes[type]?.text}</>
|
|
|
|
const Component = type === 'video' ? 'div' : Link
|
|
|
|
const Trigger = ({ component: Comp, ...props }: any) => (
|
|
<Comp
|
|
className={cn(
|
|
'py-1 flex gap-2 items-center text-white/70 hover:text-white transition-colors text-sm leading-none!',
|
|
className
|
|
)}
|
|
{...props}
|
|
>
|
|
<span className="w-1.5 h-1.5 bg-current flex items-center justify-center" />
|
|
<Text />
|
|
</Comp>
|
|
)
|
|
|
|
if (type === 'video')
|
|
return <ExpandableVideo videoId={href} trigger={<Trigger component={Component} />} />
|
|
|
|
return <Trigger href={href} target={isTargetBlank() ? '_blank' : '_self'} component={Component} />
|
|
}
|
|
|
|
export default {
|
|
DayLink,
|
|
}
|