mirror of
https://github.com/supabase/supabase.git
synced 2026-09-08 19:08:44 +08: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
350 lines
15 KiB
TypeScript
350 lines
15 KiB
TypeScript
import { readFile } from 'node:fs/promises'
|
|
import { join } from 'node:path'
|
|
import { TabPanel, Tabs } from '~/features/ui/Tabs'
|
|
import { GENERATED_DIRECTORY } from '~/lib/docs'
|
|
import { codeBlock } from 'common-tags'
|
|
import { Check, PlusCircle } from 'lucide-react'
|
|
import ReactMarkdown from 'react-markdown'
|
|
import { Heading, Popover, PopoverContent, PopoverTrigger } from 'ui'
|
|
import { CodeBlock } from 'ui-patterns/CodeBlock'
|
|
|
|
function ProviderSettings({ schema }: { schema: any }) {
|
|
const attributes = schema.block.attributes
|
|
|
|
const example = codeBlock`
|
|
provider "supabase" {
|
|
${Object.keys(attributes).map(
|
|
(attribute) =>
|
|
`${attribute} = ${attributes[attribute].type === 'string' ? `""` : '<value>'}`
|
|
)}
|
|
}
|
|
`
|
|
|
|
return (
|
|
<section aria-labelledby="provider-settings" className="prose max-w-none">
|
|
<Heading tag="h2">Provider settings</Heading>
|
|
<p>
|
|
Use these settings to configure your Supabase provider and authenticate to your Supabase
|
|
project.
|
|
</p>
|
|
<Heading tag="h3">Example usage</Heading>
|
|
<CodeBlock className="not-prose">{example}</CodeBlock>
|
|
<Heading tag="h3">Details</Heading>
|
|
{/* extra div because width restriction doesn't work on table itself */}
|
|
<div className="w-full overflow-auto">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Attribute</th>
|
|
<th>Description</th>
|
|
<th>Type</th>
|
|
<th>Optional</th>
|
|
<th>Sensitive</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{Object.keys(schema.block.attributes).map((attribute) => (
|
|
<tr key={attribute}>
|
|
<td>{attribute}</td>
|
|
<td>
|
|
<ReactMarkdown>{attributes[attribute].description}</ReactMarkdown>
|
|
</td>
|
|
<td>{attributes[attribute].type}</td>
|
|
<td className="align-middle">
|
|
{attributes[attribute].optional && (
|
|
<>
|
|
<Check className="ml-[2.5ch]" />
|
|
<span className="sr-only">true</span>
|
|
</>
|
|
)}
|
|
</td>
|
|
<td className="align-middle">
|
|
{attributes[attribute].sensitive && (
|
|
<>
|
|
<Check className="ml-[2.5ch]" />
|
|
<span className="sr-only">true</span>
|
|
</>
|
|
)}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
)
|
|
}
|
|
|
|
function Resources({ schema }: { schema: any }) {
|
|
return (
|
|
<section aria-labelledby="resources" className="prose max-w-none">
|
|
<Heading tag="h2">Resources</Heading>
|
|
<p>You can configure these resources using the Supabase Terraform provider:</p>
|
|
<Tabs>
|
|
{Object.keys(schema).map((resource) => (
|
|
<TabPanel id={resource} label={resource}>
|
|
<Heading tag="h4">Example usage</Heading>
|
|
<CodeBlock className="not-prose">{codeBlock`
|
|
resource "${resource}" "<label>" {
|
|
${Object.keys(schema[resource].block.attributes)
|
|
.filter((attribute) => !schema[resource].block.attributes[attribute].computed)
|
|
.map(
|
|
(attribute) =>
|
|
`${attribute} = ${
|
|
schema[resource].block.attributes[attribute].type === 'string'
|
|
? `""`
|
|
: '<value>'
|
|
}`
|
|
)}
|
|
}
|
|
`}</CodeBlock>
|
|
<Heading tag="h4">Details</Heading>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Attribute</th>
|
|
<th>Description</th>
|
|
<th>Type</th>
|
|
<th>Required</th>
|
|
<th>Optional</th>
|
|
<th>Read-only</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{Object.keys(schema[resource].block.attributes).map((attribute) => (
|
|
<tr key={attribute}>
|
|
<td>
|
|
<code>{attribute}</code>
|
|
</td>
|
|
<td>
|
|
<ReactMarkdown>
|
|
{schema[resource].block.attributes[attribute].description}
|
|
</ReactMarkdown>
|
|
</td>
|
|
<td>
|
|
{schema[resource].block.attributes[attribute].type ?? (
|
|
<Popover>
|
|
<PopoverTrigger asChild>
|
|
<button
|
|
tabIndex={0}
|
|
className="flex items-center justify-between gap-2"
|
|
>
|
|
Nested type
|
|
<PlusCircle size={14} className="shrink-0" />
|
|
</button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="max-h-[50vh] overflow-auto">
|
|
<ul>
|
|
{Object.keys(
|
|
schema[resource].block.attributes[attribute].nested_type.attributes
|
|
).map((nestedAttribute) => (
|
|
<li key={nestedAttribute}>
|
|
{nestedAttribute}
|
|
<ul>
|
|
<li className="*:m-0!">
|
|
<ReactMarkdown>
|
|
{
|
|
schema[resource].block.attributes[attribute].nested_type
|
|
.attributes[nestedAttribute].description
|
|
}
|
|
</ReactMarkdown>
|
|
</li>
|
|
<li>
|
|
{schema[resource].block.attributes[attribute].nested_type
|
|
.attributes[nestedAttribute].type ?? 'nested type'}
|
|
</li>
|
|
{schema[resource].block.attributes[attribute].nested_type
|
|
.attributes[nestedAttribute].required && <li>Required</li>}
|
|
{schema[resource].block.attributes[attribute].nested_type
|
|
.attributes[nestedAttribute].optional && <li>Optional</li>}
|
|
{schema[resource].block.attributes[attribute].nested_type
|
|
.attributes[nestedAttribute].computed && <li>Read-only</li>}
|
|
</ul>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</PopoverContent>
|
|
</Popover>
|
|
)}
|
|
</td>
|
|
<td className="align-middle">
|
|
{schema[resource].block.attributes[attribute].required && (
|
|
<>
|
|
<Check className="ml-[2.5ch]" />
|
|
<span className="sr-only">true</span>
|
|
</>
|
|
)}
|
|
</td>
|
|
<td className="align-middle">
|
|
{schema[resource].block.attributes[attribute].optional && (
|
|
<>
|
|
<Check className="ml-[2.5ch]" />
|
|
<span className="sr-only">true</span>
|
|
</>
|
|
)}
|
|
</td>
|
|
<td className="align-middle">
|
|
{schema[resource].block.attributes[attribute].computed && (
|
|
<>
|
|
<Check className="ml-[2.5ch]" />
|
|
<span className="sr-only">true</span>
|
|
</>
|
|
)}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</TabPanel>
|
|
))}
|
|
</Tabs>
|
|
</section>
|
|
)
|
|
}
|
|
|
|
function DataSources({ schema }: { schema: any }) {
|
|
return (
|
|
<section aria-labelledby="data-sources" className="prose max-w-none">
|
|
<Heading tag="h2">Data sources</Heading>
|
|
<p>You can read these resources using the Supabase Terraform provider:</p>
|
|
<Tabs>
|
|
{Object.keys(schema).map((dataSource) => (
|
|
<TabPanel id={dataSource} label={dataSource}>
|
|
<Heading tag="h4">Example usage</Heading>
|
|
<CodeBlock className="not-prose">{codeBlock`
|
|
resource "${dataSource}" "all" {
|
|
${Object.keys(schema[dataSource].block.attributes)
|
|
.filter(
|
|
(attribute) => !schema[dataSource].block.attributes[attribute].computed
|
|
)
|
|
.map(
|
|
(attribute) =>
|
|
`${attribute} = ${
|
|
schema[dataSource].block.attributes[attribute].type === 'string'
|
|
? `""`
|
|
: '<value>'
|
|
}`
|
|
)}
|
|
}
|
|
`}</CodeBlock>
|
|
<Heading tag="h4">Details</Heading>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Attribute</th>
|
|
<th>Description</th>
|
|
<th>Type</th>
|
|
<th>Required</th>
|
|
<th>Optional</th>
|
|
<th>Read-only</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{Object.keys(schema[dataSource].block.attributes).map((attribute) => (
|
|
<tr key={attribute}>
|
|
<td>
|
|
<code>{attribute}</code>
|
|
</td>
|
|
<td>
|
|
<ReactMarkdown>
|
|
{schema[dataSource].block.attributes[attribute].description}
|
|
</ReactMarkdown>
|
|
</td>
|
|
<td>
|
|
{schema[dataSource].block.attributes[attribute].type ?? (
|
|
<Popover>
|
|
<PopoverTrigger asChild>
|
|
<button
|
|
tabIndex={0}
|
|
className="flex items-center justify-between gap-2"
|
|
>
|
|
Nested type
|
|
<PlusCircle size={14} />
|
|
</button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="max-h-[50vh] overflow-auto">
|
|
{schema[dataSource].block.attributes[attribute].nested_type
|
|
.nesting_mode === 'set' && 'Array of:'}
|
|
<ul>
|
|
{Object.keys(
|
|
schema[dataSource].block.attributes[attribute].nested_type
|
|
.attributes
|
|
).map((nestedAttribute) => (
|
|
<li key={nestedAttribute}>
|
|
{nestedAttribute}
|
|
<ul>
|
|
<li className="*:m-0!">
|
|
<ReactMarkdown>
|
|
{
|
|
schema[dataSource].block.attributes[attribute].nested_type
|
|
.attributes[nestedAttribute].description
|
|
}
|
|
</ReactMarkdown>
|
|
</li>
|
|
<li>
|
|
{schema[dataSource].block.attributes[attribute].nested_type
|
|
.attributes[nestedAttribute].type ?? 'nested type'}
|
|
</li>
|
|
{schema[dataSource].block.attributes[attribute].nested_type
|
|
.attributes[nestedAttribute].required && <li>Required</li>}
|
|
{schema[dataSource].block.attributes[attribute].nested_type
|
|
.attributes[nestedAttribute].optional && <li>Optional</li>}
|
|
{schema[dataSource].block.attributes[attribute].nested_type
|
|
.attributes[nestedAttribute].computed && <li>Read-only</li>}
|
|
</ul>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</PopoverContent>
|
|
</Popover>
|
|
)}
|
|
</td>
|
|
<td className="align-middle">
|
|
{schema[dataSource].block.attributes[attribute].required && (
|
|
<>
|
|
<Check className="ml-[2.5ch]" />
|
|
<span className="sr-only">true</span>
|
|
</>
|
|
)}
|
|
</td>
|
|
<td className="align-middle">
|
|
{schema[dataSource].block.attributes[attribute].optional && (
|
|
<>
|
|
<Check className="ml-[2.5ch]" />
|
|
<span className="sr-only">true</span>
|
|
</>
|
|
)}
|
|
</td>
|
|
<td className="align-middle">
|
|
{schema[dataSource].block.attributes[attribute].computed && (
|
|
<>
|
|
<Check className="ml-[2.5ch]" />
|
|
<span className="sr-only">true</span>
|
|
</>
|
|
)}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</TabPanel>
|
|
))}
|
|
</Tabs>
|
|
</section>
|
|
)
|
|
}
|
|
|
|
export async function TerraformProviderSchema() {
|
|
const raw = await readFile(join(GENERATED_DIRECTORY, 'terraform.schema.json'), 'utf-8')
|
|
const schema = JSON.parse(raw)
|
|
const providerSchema = schema.provider_schemas['registry.terraform.io/supabase/supabase']
|
|
|
|
return (
|
|
<>
|
|
<ProviderSettings schema={providerSchema.provider} />
|
|
<Resources schema={providerSchema.resource_schemas} />
|
|
<DataSources schema={providerSchema.data_source_schemas} />
|
|
</>
|
|
)
|
|
}
|