mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 01:15:03 +03:00
fix(docs): button a11y enhancements (#49952)
## What kind of change does this PR introduce? bug fix for accessibility, fixes [docs-1265](https://linear.app/supabase/issue/DOCS-1365/add-aria-label-to-icon-only-buttons-button-name) ## What is the current behavior? some controles in the docs app have no accessible name, so screen readers announce them as an unlabelled "button" ## What is the new behavior? - adds each control a name the way its neighbours already do - adds menu toggle a state-dependent `aria-label` and `aria-expanded` - adds `button-name` to `enforced_rules` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Accessibility** - Added descriptive labels to configuration selectors and mobile menu controls. - Mobile menu controls now announce whether the menu is open or closed. - Expanded accessibility validation to check button names across documentation pages. - **UI Improvements** - Updated the raw throughput table control with a clearer button appearance and expandable chevron indicator. - Replaced a schema-migration tooltip with a direct in-page link. - **Documentation** - Removed the Info Tooltip component guidance from the contributing documentation. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
c1df3711a6
commit
085d218bff
8 files changed
+18
-157
No files matched your search
@@ -472,14 +472,6 @@ void main() async {
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Info Tooltip
|
||||
|
||||
The <InfoTooltip tooltipContent="This is an info tooltip">InfoTooltip</InfoTooltip> component is used to add more context to a word or phrase via tooltip.
|
||||
|
||||
```mdx
|
||||
<InfoTooltip tooltipContent="The Postgres Development Platform.">Supabase</InfoTooltip>
|
||||
```
|
||||
|
||||
## Partials
|
||||
|
||||
We incorporate content reuse in the docs to avoid duplication. If you find yourself writing the same content over and over, you can put it in a partial instead. Here are some examples of commonly used partials:
|
||||
|
||||
@@ -86,6 +86,7 @@ export function ComboBox<Opt extends ComboBoxOption>({
|
||||
role="combobox"
|
||||
disabled={disabled}
|
||||
aria-expanded={open}
|
||||
aria-label={`Select your ${name}`}
|
||||
className={cn(
|
||||
'overflow-hidden',
|
||||
'h-auto min-h-10',
|
||||
|
||||
@@ -2,7 +2,7 @@ import throughputTable from '~/data/realtime/throughput.json'
|
||||
import { ChevronDown } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
Button,
|
||||
cn,
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
@@ -138,22 +138,17 @@ export default function RealtimeLimitsEstimater({}) {
|
||||
)}
|
||||
|
||||
<Collapsible open={expandPreview} onOpenChange={setExpandPreview}>
|
||||
<CollapsibleTrigger asChild>
|
||||
<div className="py-1 flex items-center">
|
||||
<p className="text-sm">View raw throughput table</p>
|
||||
<Button
|
||||
variant="text"
|
||||
icon={
|
||||
<ChevronDown
|
||||
size={18}
|
||||
strokeWidth={2}
|
||||
className={expandPreview ? 'rotate-180' : undefined}
|
||||
/>
|
||||
}
|
||||
className="px-1"
|
||||
onClick={() => setExpandPreview(!expandPreview)}
|
||||
/>
|
||||
</div>
|
||||
<CollapsibleTrigger className="group text-base py-1 flex items-center gap-1 cursor-pointer select-none mb-10 text-foreground-light hover:text-foreground transition-colors">
|
||||
View raw throughput table
|
||||
<ChevronDown
|
||||
size={14}
|
||||
strokeWidth={2}
|
||||
aria-hidden
|
||||
className={cn(
|
||||
'text-foreground-lighter group-hover:text-foreground transition-colors',
|
||||
expandPreview && 'rotate-180'
|
||||
)}
|
||||
/>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<div>
|
||||
|
||||
@@ -7,7 +7,7 @@ subtitle: 'Manage your database schemas in one place and generate versioned migr
|
||||
|
||||
## Overview
|
||||
|
||||
Declarative schemas provide a developer-friendly way to maintain <InfoTooltip tooltipContent={<><p>Files of SQL statements that track the evolution of your database schema over time.<br />They allow you to version control your database schema alongside your application code.</p><p>See the <Link href="/guides/deployment/database-migrations" className="underline">database migrations</Link> guide to learn more.</p></>}>schema migrations</InfoTooltip>.
|
||||
Declarative schemas provide a developer-friendly way to maintain [schema migrations](#schema-migrations).
|
||||
|
||||
[Migrations](/docs/guides/deployment/database-migrations) are traditionally managed imperatively (you provide the instructions on how exactly to change the database). This can lead to related information being scattered over multiple migration files. With declarative schemas, you instead declare the state you want your database to be in, and the instructions are generated for you.
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ import { NamedCodeBlock } from '~/features/directives/CodeTabs.components'
|
||||
import { MdxAnchor } from '~/features/docs/MdxAnchor'
|
||||
import { Accordion, AccordionItem } from '~/features/ui/Accordion'
|
||||
import { CodeBlock } from '~/features/ui/CodeBlock/CodeBlock'
|
||||
import InfoTooltip from '~/features/ui/InfoTooltip'
|
||||
import { ShowUntil } from '~/features/ui/ShowUntil'
|
||||
import { TabPanel, Tabs } from '~/features/ui/Tabs'
|
||||
import { ArrowDown, Check, X } from 'lucide-react'
|
||||
@@ -114,7 +113,6 @@ const components = {
|
||||
TabPanel,
|
||||
TerraformProviderSchema,
|
||||
WrapperDashboardIntegration,
|
||||
InfoTooltip,
|
||||
a: MdxAnchor,
|
||||
h2: (props: ComponentPropsWithoutRef<'h2'>) => (
|
||||
<Heading tag="h2" {...props}>
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useBreakpoint } from 'common'
|
||||
import { InfoIcon, XIcon } from 'lucide-react'
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useId,
|
||||
useRef,
|
||||
useState,
|
||||
type PropsWithChildren,
|
||||
} from 'react'
|
||||
import { ErrorBoundary } from 'react-error-boundary'
|
||||
import {
|
||||
Button,
|
||||
cn,
|
||||
CommandEmpty,
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from 'ui'
|
||||
|
||||
interface PopUpProps extends PropsWithChildren {
|
||||
tooltipContent: React.ReactNode
|
||||
className?: string
|
||||
contentContainerClassName?: string
|
||||
}
|
||||
|
||||
const buttonClassName = cn(
|
||||
'relative px-1 py-0 -my-px',
|
||||
'rounded-sm bg-surface-200 border border-dashed',
|
||||
'transition-colors hover:border-strong group/inline-popup'
|
||||
)
|
||||
|
||||
const InfoTooltip = ({
|
||||
children,
|
||||
className,
|
||||
tooltipContent,
|
||||
contentContainerClassName,
|
||||
}: PopUpProps) => {
|
||||
const id = useId().replaceAll(':', '')
|
||||
const timeout = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
|
||||
const [mobileSheetOpen, setMobileSheetOpen] = useState(false)
|
||||
const [tooltipOpen, _setTooltipOpen] = useState(false)
|
||||
const isMobile = useBreakpoint('md')
|
||||
|
||||
const setTooltipOpen = useCallback(
|
||||
(open: boolean) => {
|
||||
_setTooltipOpen(open)
|
||||
setMobileSheetOpen(true)
|
||||
|
||||
timeout.current = setTimeout(() => {
|
||||
if (isMobile) return
|
||||
const targetElem: HTMLElement | null = document.querySelector(`#tooltip-content-${id}`)
|
||||
targetElem?.focus()
|
||||
})
|
||||
},
|
||||
[_setTooltipOpen, id, isMobile]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (timeout.current) {
|
||||
clearTimeout(timeout.current)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip open={tooltipOpen} onOpenChange={(open) => !isMobile && setTooltipOpen(open)}>
|
||||
<TooltipTrigger asChild>
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => setMobileSheetOpen(true)}
|
||||
className={cn(buttonClassName, className)}
|
||||
>
|
||||
{children}
|
||||
<InfoIcon
|
||||
aria-hidden={true}
|
||||
className="absolute p-px bg-background rounded-full -left-1.5 -top-1.5 w-3 h-3 text-foreground-lighter group-hover/inline-popup:text-foreground-light transition-colors"
|
||||
/>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent id={`tooltip-content-${id}`} className={contentContainerClassName}>
|
||||
{tooltipContent}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
{isMobile && (
|
||||
<Sheet open={mobileSheetOpen} onOpenChange={setMobileSheetOpen}>
|
||||
<SheetContent
|
||||
id={`mobile-sheet-content-${id}`}
|
||||
showClose={false}
|
||||
size="full"
|
||||
side="bottom"
|
||||
className={cn(
|
||||
'md:hidden',
|
||||
'text-foreground-lighter',
|
||||
'rounded-t-lg overflow-hidden overflow-y-scroll',
|
||||
'w-full h-fit min-h-[200px] py-2 px-4'
|
||||
)}
|
||||
>
|
||||
<ErrorBoundary FallbackComponent={() => <CommandEmpty />}>
|
||||
<SheetHeader className="flex items-center justify-between gap-2 px-0 py-2 mb-2 max-w-full">
|
||||
<div className="flex items-center gap-2 max-w-[90%]">
|
||||
<InfoIcon className="p-px min-w-4 min-h-4 text-foreground-lighter" />
|
||||
<p className="italic text-foreground-light truncate">{children}</p>
|
||||
</div>
|
||||
<Button variant="text" onClick={() => setMobileSheetOpen(false)} className="px-1">
|
||||
<XIcon className="w-4 h-4 text-foreground-lighter" />
|
||||
</Button>
|
||||
</SheetHeader>
|
||||
{tooltipContent}
|
||||
</ErrorBoundary>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default InfoTooltip
|
||||
@@ -214,6 +214,8 @@ const MobileHeader = memo(function MobileHeader(props: MobileHeaderProps) {
|
||||
mobileMenuOpen && 'mt-0.5'
|
||||
)}
|
||||
onClick={() => menuState.setMenuMobileOpen(!mobileMenuOpen)}
|
||||
aria-label={mobileMenuOpen ? 'Close menu' : 'Open menu'}
|
||||
aria-expanded={mobileMenuOpen}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
|
||||
@@ -5,7 +5,7 @@ import { scan } from '../../shared/axe.ts'
|
||||
|
||||
export const WCAG_TAGS = ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']
|
||||
|
||||
export const ENFORCED_RULES = ['heading-order', 'page-has-heading-one']
|
||||
export const ENFORCED_RULES = ['heading-order', 'page-has-heading-one', 'button-name']
|
||||
|
||||
export const EXCLUDED_RULES = [
|
||||
'color-contrast',
|
||||
@@ -40,7 +40,7 @@ export async function scanArticle(
|
||||
include: string
|
||||
): Promise<A11yScanResult> {
|
||||
const reported = await scan(page, { tags: WCAG_TAGS, excludeRules: EXCLUDED_RULES, include })
|
||||
const enforced = await scan(page, { rules: ENFORCED_RULES, include })
|
||||
const enforced = await scan(page, { rules: ENFORCED_RULES })
|
||||
|
||||
const byRule = new Map([...reported, ...enforced].map((violation) => [violation.id, violation]))
|
||||
|
||||
|
||||
Reference in new issue
Block a user