mirror of
https://github.com/supabase/supabase.git
synced 2026-10-12 21:05:06 +03:00
fix(studio): polish extension installation dialogs (#51463)
## Problem The enable-extension dialog in #51440 needs clearer installation copy and a consistent disclosure for custom-schema guidance. On narrow screens, footer buttons touch and dialog content can scroll sideways. ## Solution Polish the dialog on top of #51440: - Explain fixed-schema requirements and hide custom-schema guidance when the extension requires a specific schema. - Share the disclosure interaction with the session-expired and API-hardening dialogs through `ui-patterns/DialogDisclosure`, documented with a live design-system example. - Fix narrow-screen overflow and add vertical spacing to shared dialog footers. - Refine inline-code styling and update the PostGIS instructions and E2E button locator. | After | | --- | | <img width="852" height="710" alt="CleanShot 2026-10-09 at 12 17 47@2x" src="https://github.com/user-attachments/assets/59f6f1e7-9884-4cdb-b678-62b7f88a8ec5" /> | | _Standard extension_ | | <img width="836" height="1092" alt="52254" src="https://github.com/user-attachments/assets/dbf53d3e-6583-4c3b-8f43-577e0723d9a7" /> | | _Standard extension expanded accordion_ | | <img width="836" height="604" alt="CleanShot 2026-10-09 at 12 20 04@2x" src="https://github.com/user-attachments/assets/c8e3961f-2683-440c-a2a2-48ccd53c4a17" /> | | _Extension with fixed schema_ | ## Review instructions 1. Open `/project/<ref>/database/extensions`. Turn on the toggle for a disabled `postgres_fdw` extension. Expand **Install in a different schema** and check its hover, keyboard focus and full-width click target. Repeat with `pg_cron` or `pgmq`: the intro must say **must**, and custom-schema guidance must be absent. Cancel without enabling. 2. At 320 px and 375 px, check the extension dialog's alignment, inline-code rendering, wrapped SQL and stacked **Enable** / **Cancel** spacing. Confirm the page does not scroll sideways. 3. On a platform project with the Data API enabled, open `/project/<ref>/integrations/data_api/settings`. Under **Harden Data API**, click **Harden Data API**. Expand both sections and check the same trigger behaviour. Confirm completed sections still show their check icons. Close without applying either action. 4. The session-expired dialog has no direct route. It is rendered by `apps/studio/hooks/misc/withAuth.tsx` after authentication loading exceeds 10 seconds. For repeatable local review, temporarily pass `visible={true}` to its `SessionTimeoutModal` callsite, then open `/projects`. Expand **Having trouble?** by clicking the text or chevron and using Enter / Space. Check the help text and controls. Close the dialog without signing out or clearing site data, then undo the temporary prop change. 5. In the design system, open [`/docs/components/dialog#optional-details`](https://design-system-git-dnywh-choreextension-dialog-polish-supabase.vercel.app/design-system/docs/components/dialog#optional-details). Click **Export results**, then expand **What’s included** with the mouse and keyboard. The demo imports the same `ui-patterns/DialogDisclosure` used by Studio. 6. Open another dialog and an alert dialog at a narrow width. Check the shared 8 px gap between stacked footer buttons. Review inline-code examples on `/docs/components/typography` for the shared styling change.
This commit is contained in:
1 parent
255a80e219
commit
325a5a0da7
20 files changed
+273
-249
No files matched your search
@@ -127,11 +127,6 @@ export const docsConfig: DocsConfig = {
|
||||
href: '/docs/fragments/admonition',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Collapsible Alert',
|
||||
href: '/docs/fragments/collapsible-alert',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Collapsible Card Section',
|
||||
href: '/docs/fragments/collapsible-card-section',
|
||||
|
||||
@@ -11,7 +11,7 @@ Alert is the low-level shadcn primitive that forms the foundation for [Admonitio
|
||||
|
||||
Examples where Alert may be appropriate include bespoke icons, custom internal layouts, or wrapper components that need direct access to `alertVariants`.
|
||||
|
||||
Use [Collapsible Alert](../fragments/collapsible-alert) when a callout also needs expandable detail. Use [Collapsible](./collapsible) only for generic disclosure behaviour that is not itself an alert.
|
||||
Compose Alert with [Collapsible](./collapsible) when a callout needs expandable detail. Use [DialogDisclosure](./dialog#optional-details) for optional details in a dialog.
|
||||
|
||||
<ComponentPreview name="alert-demo" peekCode wide />
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ component: true
|
||||
|
||||
Collapsible is a low-level primitive for revealing and hiding related content. Use it when a component needs custom trigger or content composition.
|
||||
|
||||
Use Collapsible for generic disclosure behaviour: rows, sections, menus, tool details, and custom layouts. Use [Collapsible Alert](../fragments/collapsible-alert) instead when the disclosure is an alert-styled callout that should keep standard alert padding, variants, trigger styling, and toggle treatment.
|
||||
Use Collapsible for generic disclosure behavior: rows, sections, menus, tool details, and custom layouts. Use [DialogDisclosure](./dialog#optional-details) for optional details in a dialog.
|
||||
|
||||
<ComponentPreview name="collapsible-demo" peekCode wide />
|
||||
|
||||
|
||||
@@ -65,6 +65,14 @@ import {
|
||||
|
||||
## Examples
|
||||
|
||||
### Optional details
|
||||
|
||||
Use `DialogDisclosure` to reveal optional help or context inside a dialog. Put the trigger in the dialog body above the footer, and add a section separator when the disclosure follows other content. Keep the primary action visible while the details are open or closed.
|
||||
|
||||
`Collapsible` provides the open and closed state. `DialogDisclosure` adds the trigger and content presentation used in dialogs. Use `Accordion` when several related sections each need a heading and expandable content.
|
||||
|
||||
<ComponentPreview name="dialog-disclosure-demo" peekCode wide />
|
||||
|
||||
### Custom close button
|
||||
|
||||
<ComponentPreview name="dialog-close-button" />
|
||||
|
||||
@@ -15,7 +15,7 @@ Admonition provides the standard product callout pattern for situations that req
|
||||
|
||||
Use Admonition instead of the low-level [Alert](../components/alert) primitive unless you specifically need custom composition. If in doubt, stick with Admonition.
|
||||
|
||||
Use [Collapsible Alert](./collapsible-alert) when the callout needs to remain visible while optional detail can be expanded. Use [Collapsible](../components/collapsible) for generic disclosure behaviour that is not itself a callout.
|
||||
Use [Collapsible](../components/collapsible) for custom expandable content. Use [DialogDisclosure](../components/dialog#optional-details) for optional details in a dialog.
|
||||
|
||||
Use `title` for the heading slot when the callout needs its own heading. `title` is optional: short callouts may use `description` without a title when nearby content already provides enough context.
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
---
|
||||
title: Collapsible Alert
|
||||
description: An alert-styled block with a collapsible content section, toggled by an icon button.
|
||||
fragment: true
|
||||
---
|
||||
|
||||
Collapsible Alert combines an Alert-style container with a collapsible content section. Use it when the callout itself needs to stay visible while optional detail can be expanded.
|
||||
|
||||
Use Collapsible Alert when the disclosure is itself a callout. It owns the alert styling, padding, trigger treatment, and toggle button. Use [Collapsible](../components/collapsible) instead when you only need generic reveal and hide behaviour and the surrounding layout is not an alert.
|
||||
|
||||
For non-expandable product callouts, use [Admonition](./admonition). Use the low-level [Alert](../components/alert) primitive only when a wrapper or bespoke composition needs direct control over the alert structure.
|
||||
|
||||
<ComponentPreview name="collapsible-alert-demo" peekCode wide />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import { CollapsibleAlert } from 'ui-patterns/collapsible-alert'
|
||||
```
|
||||
|
||||
```tsx
|
||||
<CollapsibleAlert trigger="Need help?">
|
||||
<p>Content revealed when expanded.</p>
|
||||
</CollapsibleAlert>
|
||||
```
|
||||
|
||||
### Default open
|
||||
|
||||
```tsx
|
||||
<CollapsibleAlert trigger="Details" defaultOpen>
|
||||
<p>This section is expanded by default.</p>
|
||||
</CollapsibleAlert>
|
||||
```
|
||||
|
||||
### Variants
|
||||
|
||||
Supports the same `variant` prop as Alert: `default`, `destructive`, and `warning`.
|
||||
|
||||
```tsx
|
||||
<CollapsibleAlert trigger="Warning" variant="warning">
|
||||
<p>Something needs attention.</p>
|
||||
</CollapsibleAlert>
|
||||
```
|
||||
@@ -1,11 +0,0 @@
|
||||
import { CollapsibleAlert } from 'ui-patterns/collapsible-alert'
|
||||
|
||||
export default function CollapsibleAlertDemo() {
|
||||
return (
|
||||
<CollapsibleAlert trigger="Need help?">
|
||||
<p className="text-sm text-foreground-light">
|
||||
Try a different browser or disable extensions that block network requests.
|
||||
</p>
|
||||
</CollapsibleAlert>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogSection,
|
||||
DialogSectionSeparator,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from 'ui'
|
||||
import {
|
||||
DialogDisclosure,
|
||||
DialogDisclosureContent,
|
||||
DialogDisclosureTrigger,
|
||||
} from 'ui-patterns/DialogDisclosure'
|
||||
|
||||
export default function DialogDisclosureDemo() {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button>Export results</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent size="small">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Export results</DialogTitle>
|
||||
</DialogHeader>
|
||||
<DialogSectionSeparator />
|
||||
<DialogSection>
|
||||
<p className="text-sm text-foreground-light">
|
||||
All 250 rows will be downloaded as a CSV file.
|
||||
</p>
|
||||
</DialogSection>
|
||||
<DialogSectionSeparator />
|
||||
<DialogDisclosure>
|
||||
<DialogDisclosureTrigger className="px-4 py-4 md:px-5">
|
||||
What’s included
|
||||
</DialogDisclosureTrigger>
|
||||
<DialogDisclosureContent>
|
||||
<DialogSection className="pt-1 text-sm text-foreground-light space-y-3">
|
||||
<p>The file includes column names in the first row, followed by the query results.</p>
|
||||
<ul className="list-disc pl-5 space-y-1">
|
||||
<li>Columns keep the same order as the results table.</li>
|
||||
<li>Empty fields represent null values.</li>
|
||||
<li>Values containing commas or line breaks are enclosed in double quotes.</li>
|
||||
</ul>
|
||||
<p>Open the file in a spreadsheet or import it into another tool.</p>
|
||||
</DialogSection>
|
||||
</DialogDisclosureContent>
|
||||
</DialogDisclosure>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button>Cancel</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button variant="primary">Download CSV</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
@@ -67,12 +67,6 @@ export const examples: Registry = [
|
||||
registryDependencies: ['alert'],
|
||||
files: ['example/alert-demo.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'collapsible-alert-demo',
|
||||
type: 'components:example',
|
||||
registryDependencies: ['collapsible-alert'],
|
||||
files: ['example/collapsible-alert-demo.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'collapsible-card-section-demo',
|
||||
type: 'components:example',
|
||||
@@ -551,6 +545,12 @@ export const examples: Registry = [
|
||||
registryDependencies: ['dialog'],
|
||||
files: ['example/dialog-demo.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'dialog-disclosure-demo',
|
||||
type: 'components:example',
|
||||
registryDependencies: ['dialog', 'collapsible', 'button'],
|
||||
files: ['example/dialog-disclosure-demo.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'dialog-close-button',
|
||||
type: 'components:example',
|
||||
|
||||
@@ -29,7 +29,7 @@ You can get started with PostGIS by enabling the PostGIS extension in your Supab
|
||||
1. Go to the [Database](/dashboard/project/_/database/tables) page in the Dashboard.
|
||||
2. Click on **Extensions** in the sidebar.
|
||||
3. Search for `postgis` and enable the extension.
|
||||
4. In the confirmation prompt select "Create a new schema" and name it `gis` for example.
|
||||
4. In the confirmation dialog, review the default `extensions` schema and click **Enable**.
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="sql" label="SQL">
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { ident, safeSql } from '@supabase/pg-meta'
|
||||
import { toast } from 'sonner'
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
cn,
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
@@ -17,9 +15,13 @@ import {
|
||||
} from 'ui'
|
||||
import { Admonition } from 'ui-patterns/Admonition'
|
||||
import { CodeBlock } from 'ui-patterns/CodeBlock'
|
||||
import {
|
||||
DialogDisclosure,
|
||||
DialogDisclosureContent,
|
||||
DialogDisclosureTrigger,
|
||||
} from 'ui-patterns/DialogDisclosure'
|
||||
|
||||
import { DocsButton } from '@/components/ui/DocsButton'
|
||||
import { InlineLinkClassName } from '@/components/ui/InlineLink'
|
||||
import { useDatabaseExtensionEnableMutation } from '@/data/database-extensions/database-extension-enable-mutation'
|
||||
import { type DatabaseExtension } from '@/data/database-extensions/database-extensions-query'
|
||||
import { useIsOrioleDb, useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
||||
@@ -45,6 +47,7 @@ export const EnableExtensionModal = ({
|
||||
// [Joshen] Hard-coding pg_cron here as this is enforced on our end (Not via pg_available_extension_versions)
|
||||
const defaultSchema =
|
||||
extension.name === 'pg_cron' ? 'pg_catalog' : extension.default_version_schema
|
||||
const hasFixedSchema = defaultSchema != null
|
||||
|
||||
const { mutate: enableExtension, isPending: isEnabling } = useDatabaseExtensionEnableMutation({
|
||||
onSuccess: () => {
|
||||
@@ -76,9 +79,9 @@ export const EnableExtensionModal = ({
|
||||
if (!open) onCancel()
|
||||
}}
|
||||
>
|
||||
<DialogContent size="small" aria-describedby={undefined}>
|
||||
<DialogContent size="small" className="min-w-0 overflow-hidden" aria-describedby={undefined}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Confirm to enable {extension.name}</DialogTitle>
|
||||
<DialogTitle>Enable {extension.name}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogSectionSeparator />
|
||||
@@ -99,58 +102,67 @@ export const EnableExtensionModal = ({
|
||||
|
||||
<DialogSection className="flex flex-col gap-y-4">
|
||||
<p className="text-sm text-foreground-light">
|
||||
The following database extension will be enabled
|
||||
{hasFixedSchema
|
||||
? 'This extension must be installed in the schema below.'
|
||||
: 'This extension will be installed in the schema below.'}
|
||||
</p>
|
||||
|
||||
<Card>
|
||||
<CardContent className="divide-y text-sm p-0">
|
||||
<div className="flex items-center justify-between px-4 py-2">
|
||||
<div className="flex items-center justify-between px-3 py-2.5">
|
||||
<p className="text-foreground-lighter">Extension</p>
|
||||
<p className="text-foreground">{extension.name}</p>
|
||||
<code className="text-code-inline">{extension.name}</code>
|
||||
</div>
|
||||
<div className="flex items-center justify-between px-4 py-2">
|
||||
<div className="flex items-center justify-between px-3 py-2.5">
|
||||
<p className="text-foreground-lighter">Schema</p>
|
||||
<p data-testid="enable-extension-schema" className="text-foreground">
|
||||
<code data-testid="enable-extension-schema" className="text-code-inline">
|
||||
{defaultSchema ?? 'extensions'}
|
||||
</p>
|
||||
</code>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Collapsible>
|
||||
<CollapsibleTrigger
|
||||
className={cn(
|
||||
InlineLinkClassName,
|
||||
'text-xs text-foreground-lighter data-open:text-foreground-light'
|
||||
)}
|
||||
>
|
||||
Need to install this in a different schema?
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="[overflow-y:clip] data-closed:animate-collapsible-up data-open:animate-collapsible-down">
|
||||
<div className="my-2 text-xs text-foreground-light flex flex-col gap-y-1">
|
||||
<p>
|
||||
Installing in the extensions schema is highly recommended since some extensions
|
||||
are hard to move after installation.
|
||||
</p>
|
||||
<p>To use a different schema, run this in the SQL Editor:</p>
|
||||
</div>
|
||||
<CodeBlock
|
||||
language="pgsql"
|
||||
hideLineNumbers
|
||||
wrapperClassName={cn('[&_pre]:px-3 [&_pre]:py-3')}
|
||||
className="[&_code]:text-xs"
|
||||
value={`create extension if not exists ${extension.name} schema target_schema;`}
|
||||
/>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
</DialogSection>
|
||||
|
||||
{!hasFixedSchema && (
|
||||
<>
|
||||
<DialogSectionSeparator />
|
||||
<DialogDisclosure>
|
||||
<DialogDisclosureTrigger className="py-4 px-4 md:px-5">
|
||||
<span>Install in a different schema</span>
|
||||
</DialogDisclosureTrigger>
|
||||
<DialogDisclosureContent>
|
||||
<DialogSection className="pt-1 min-w-0 flex flex-col gap-y-3">
|
||||
<div className="text-sm text-foreground-light flex flex-col gap-y-3">
|
||||
<p>
|
||||
We recommend using the <code className="text-code-inline">extensions</code>{' '}
|
||||
schema to keep extension objects separate from your own tables.
|
||||
</p>
|
||||
<p>
|
||||
To use another schema, run the following in the SQL Editor. Replace{' '}
|
||||
<code className="text-code-inline">target_schema</code> with an existing
|
||||
schema name.
|
||||
</p>
|
||||
</div>
|
||||
<CodeBlock
|
||||
language="pgsql"
|
||||
hideLineNumbers
|
||||
wrapLongLines
|
||||
wrapperClassName={cn('min-w-0 max-w-full [&_pre]:px-3 [&_pre]:py-3')}
|
||||
className="[&_code]:text-xs"
|
||||
value={safeSql`create extension if not exists ${ident(extension.name)} schema target_schema;`}
|
||||
/>
|
||||
</DialogSection>
|
||||
</DialogDisclosureContent>
|
||||
</DialogDisclosure>
|
||||
</>
|
||||
)}
|
||||
|
||||
<DialogFooter>
|
||||
<Button disabled={isEnabling} onClick={() => onCancel()}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="primary" loading={isEnabling} onClick={() => onConfirmEnable()}>
|
||||
Enable extension
|
||||
Enable
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { Check, ChevronDown } from 'lucide-react'
|
||||
import { Check } from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
AlertTitle,
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
@@ -17,6 +14,11 @@ import {
|
||||
WarningIcon,
|
||||
} from 'ui'
|
||||
import { CodeBlock } from 'ui-patterns/CodeBlock'
|
||||
import {
|
||||
DialogDisclosure,
|
||||
DialogDisclosureContent,
|
||||
DialogDisclosureTrigger,
|
||||
} from 'ui-patterns/DialogDisclosure'
|
||||
|
||||
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
|
||||
import { DocsButton } from '@/components/ui/DocsButton'
|
||||
@@ -129,21 +131,18 @@ export const HardenAPIModal = ({ visible, onClose }: HardenAPIModalProps) => {
|
||||
|
||||
<DialogSectionSeparator />
|
||||
|
||||
<Collapsible>
|
||||
<CollapsibleTrigger className="py-4 px-5 w-full flex items-center justify-between text-sm">
|
||||
<p>
|
||||
1. Create a custom <code className="text-code-inline">api</code> schema and expose it
|
||||
</p>
|
||||
{hasAPISchema && isAPISchemaExposed ? (
|
||||
<Check size={16} className="text-primary" />
|
||||
) : (
|
||||
<ChevronDown
|
||||
size={16}
|
||||
className="transition data-open-parent:rotate-180 data-closed-parent:rotate-0"
|
||||
/>
|
||||
)}
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="text-sm text-foreground-light flex flex-col gap-y-4">
|
||||
<DialogDisclosure>
|
||||
<DialogDisclosureTrigger
|
||||
className="py-4 px-5"
|
||||
icon={
|
||||
hasAPISchema && isAPISchemaExposed ? (
|
||||
<Check size={16} className="text-primary" />
|
||||
) : undefined
|
||||
}
|
||||
>
|
||||
1. Create a custom <code className="text-code-inline">api</code> schema and expose it
|
||||
</DialogDisclosureTrigger>
|
||||
<DialogDisclosureContent className="text-sm text-foreground-light flex flex-col gap-y-4">
|
||||
<p className="mx-5">
|
||||
Click the button below to create a new schema named{' '}
|
||||
<code className="text-code-inline">api</code> and grant the{' '}
|
||||
@@ -207,27 +206,20 @@ export const HardenAPIModal = ({ visible, onClose }: HardenAPIModalProps) => {
|
||||
{`grant select on table api.<your_table> to anon;\ngrant select, insert, update, delete on table api.<your_table> to authenticated;`}
|
||||
</CodeBlock>
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
</DialogDisclosureContent>
|
||||
</DialogDisclosure>
|
||||
|
||||
<DialogSectionSeparator />
|
||||
|
||||
<Collapsible>
|
||||
<CollapsibleTrigger className="py-4 px-5 w-full flex items-center justify-between text-sm">
|
||||
<p>
|
||||
2. Remove the <code className="text-code-inline">public</code> schema from the exposed
|
||||
schemas
|
||||
</p>
|
||||
{!isPublicSchemaExposed ? (
|
||||
<Check size={16} className="text-primary" />
|
||||
) : (
|
||||
<ChevronDown
|
||||
size={16}
|
||||
className="transition data-open-parent:rotate-180 data-closed-parent:rotate-0"
|
||||
/>
|
||||
)}
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="text-sm text-foreground-light">
|
||||
<DialogDisclosure>
|
||||
<DialogDisclosureTrigger
|
||||
className="py-4 px-5"
|
||||
icon={!isPublicSchemaExposed ? <Check size={16} className="text-primary" /> : undefined}
|
||||
>
|
||||
2. Remove the <code className="text-code-inline">public</code> schema from the exposed
|
||||
schemas
|
||||
</DialogDisclosureTrigger>
|
||||
<DialogDisclosureContent className="text-sm text-foreground-light">
|
||||
<div className="px-5 pb-4 flex flex-col gap-y-4">
|
||||
<Alert variant="warning">
|
||||
<WarningIcon />
|
||||
@@ -262,8 +254,8 @@ export const HardenAPIModal = ({ visible, onClose }: HardenAPIModalProps) => {
|
||||
Remove public schema from exposed schemas
|
||||
</ButtonTooltip>
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
</DialogDisclosureContent>
|
||||
</DialogDisclosure>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
|
||||
@@ -12,8 +12,14 @@ import {
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
Button,
|
||||
DialogSection,
|
||||
DialogSectionSeparator,
|
||||
} from 'ui'
|
||||
import { CollapsibleAlert } from 'ui-patterns/collapsible-alert'
|
||||
import {
|
||||
DialogDisclosure,
|
||||
DialogDisclosureContent,
|
||||
DialogDisclosureTrigger,
|
||||
} from 'ui-patterns/DialogDisclosure'
|
||||
|
||||
import { SupportLink } from '../Support/SupportLink'
|
||||
import { InlineLink, InlineLinkClassName } from '@/components/ui/InlineLink'
|
||||
@@ -54,45 +60,47 @@ export const SessionTimeoutModal = ({
|
||||
<AlertDialogContent size="small">
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Session expired</AlertDialogTitle>
|
||||
<AlertDialogDescription asChild>
|
||||
<div className="space-y-4">
|
||||
<p>Please sign in again to continue.</p>
|
||||
<CollapsibleAlert trigger="Having trouble?">
|
||||
<div className="space-y-3 text-foreground-light">
|
||||
<p>
|
||||
Try a different browser or disable extensions that block network requests. If
|
||||
the problem persists:
|
||||
</p>
|
||||
<Button size="tiny" onClick={handleClearStorage}>
|
||||
Clear site data and reload
|
||||
</Button>
|
||||
<p>
|
||||
Still stuck?{' '}
|
||||
<SupportLink
|
||||
className={InlineLinkClassName}
|
||||
queryParams={{
|
||||
subject: 'Session expired',
|
||||
category: SupportCategories.LOGIN_ISSUES,
|
||||
...(supportContext?.projectRef && {
|
||||
projectRef: supportContext.projectRef,
|
||||
}),
|
||||
...(supportContext?.orgSlug && { orgSlug: supportContext.orgSlug }),
|
||||
}}
|
||||
onClick={onClose}
|
||||
>
|
||||
Contact support
|
||||
</SupportLink>{' '}
|
||||
and include a{' '}
|
||||
<InlineLink href="https://github.com/orgs/supabase/discussions/36540">
|
||||
HAR file
|
||||
</InlineLink>{' '}
|
||||
from your session to help us investigate.
|
||||
</p>
|
||||
</div>
|
||||
</CollapsibleAlert>
|
||||
</div>
|
||||
</AlertDialogDescription>
|
||||
<AlertDialogDescription>Please sign in again to continue.</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<DialogSectionSeparator />
|
||||
<DialogDisclosure>
|
||||
<DialogDisclosureTrigger className="py-4 px-5">Having trouble?</DialogDisclosureTrigger>
|
||||
<DialogDisclosureContent>
|
||||
<DialogSection className="px-5 pt-1 text-sm">
|
||||
<div className="space-y-3 text-foreground-light">
|
||||
<p>
|
||||
Try a different browser or disable extensions that block network requests. If the
|
||||
problem persists:
|
||||
</p>
|
||||
<Button size="tiny" onClick={handleClearStorage}>
|
||||
Clear site data and reload
|
||||
</Button>
|
||||
<p>
|
||||
Still stuck?{' '}
|
||||
<SupportLink
|
||||
className={InlineLinkClassName}
|
||||
queryParams={{
|
||||
subject: 'Session expired',
|
||||
category: SupportCategories.LOGIN_ISSUES,
|
||||
...(supportContext?.projectRef && {
|
||||
projectRef: supportContext.projectRef,
|
||||
}),
|
||||
...(supportContext?.orgSlug && { orgSlug: supportContext.orgSlug }),
|
||||
}}
|
||||
onClick={onClose}
|
||||
>
|
||||
Contact support
|
||||
</SupportLink>{' '}
|
||||
and include a{' '}
|
||||
<InlineLink href="https://github.com/orgs/supabase/discussions/36540">
|
||||
HAR file
|
||||
</InlineLink>{' '}
|
||||
from your session to help us investigate.
|
||||
</p>
|
||||
</div>
|
||||
</DialogSection>
|
||||
</DialogDisclosureContent>
|
||||
</DialogDisclosure>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Close</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={redirectToSignIn}>Sign in again</AlertDialogAction>
|
||||
|
||||
@@ -900,7 +900,7 @@ test.describe('Database Extensions', () => {
|
||||
const dialog = page.getByRole('dialog')
|
||||
await expect(dialog, 'Enable extension dialog should be visible').toBeVisible()
|
||||
await expect(
|
||||
dialog.getByText(`Confirm to enable ${EXTENSION_NAME}`),
|
||||
dialog.getByRole('heading', { name: `Enable ${EXTENSION_NAME}`, exact: true }),
|
||||
'Dialog title should match extension name'
|
||||
).toBeVisible()
|
||||
await expect(
|
||||
@@ -915,7 +915,7 @@ test.describe('Database Extensions', () => {
|
||||
ref,
|
||||
'query?key=database-extensions'
|
||||
)
|
||||
await dialog.getByRole('button', { name: 'Enable extension' }).click()
|
||||
await dialog.getByRole('button', { name: 'Enable', exact: true }).click()
|
||||
await enableWait
|
||||
await refetchWait
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
|
||||
/* Pair with `code` elements for semantic consistency, e.g. <code className="text-code-inline"> */
|
||||
@utility text-code-inline {
|
||||
@apply break-all text-xs tracking-tight bg-surface-200 border border-muted rounded-md px-1 py-0.5 text-foreground font-medium;
|
||||
@apply break-all text-xs tracking-tight bg-surface-200 border border-muted rounded-sm px-1 py-0.25 text-foreground font-medium;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
|
||||
@@ -298,6 +298,10 @@
|
||||
"import": "./src/DatePicker/index.ts",
|
||||
"types": "./src/DatePicker/index.ts"
|
||||
},
|
||||
"./DialogDisclosure": {
|
||||
"import": "./src/DialogDisclosure.tsx",
|
||||
"types": "./src/DialogDisclosure.tsx"
|
||||
},
|
||||
"./Dialogs/ConfirmationModal": {
|
||||
"import": "./src/Dialogs/ConfirmationModal.tsx",
|
||||
"types": "./src/Dialogs/ConfirmationModal.tsx"
|
||||
@@ -702,10 +706,6 @@
|
||||
"import": "./src/TweetCard/index.tsx",
|
||||
"types": "./src/TweetCard/index.tsx"
|
||||
},
|
||||
"./collapsible-alert": {
|
||||
"import": "./src/collapsible-alert.tsx",
|
||||
"types": "./src/collapsible-alert.tsx"
|
||||
},
|
||||
"./consent": {
|
||||
"import": "./src/consent.tsx",
|
||||
"types": "./src/consent.tsx"
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { ChevronDown } from 'lucide-react'
|
||||
import type { ComponentProps, ReactNode } from 'react'
|
||||
import { cn, Collapsible, CollapsibleContent, CollapsibleTrigger } from 'ui'
|
||||
|
||||
type DialogDisclosureTriggerProps = ComponentProps<typeof CollapsibleTrigger> & {
|
||||
icon?: ReactNode
|
||||
}
|
||||
|
||||
export const DialogDisclosure: typeof Collapsible = Collapsible
|
||||
|
||||
export const DialogDisclosureTrigger = ({
|
||||
children,
|
||||
className,
|
||||
icon,
|
||||
...props
|
||||
}: DialogDisclosureTriggerProps) => (
|
||||
<CollapsibleTrigger
|
||||
className={cn(
|
||||
'group/trigger flex w-full cursor-pointer items-center justify-between gap-2 text-left text-sm text-foreground-light transition-colors hover:bg-muted hover:text-foreground motion-reduce:transition-none',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span>{children}</span>
|
||||
<span aria-hidden="true" className="shrink-0">
|
||||
{icon ?? (
|
||||
<ChevronDown
|
||||
size={16}
|
||||
className="transition-transform group-data-open/trigger:rotate-180 motion-reduce:transition-none"
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</CollapsibleTrigger>
|
||||
)
|
||||
|
||||
export const DialogDisclosureContent = ({
|
||||
className,
|
||||
...props
|
||||
}: ComponentProps<typeof CollapsibleContent>) => (
|
||||
<CollapsibleContent
|
||||
className={cn(
|
||||
'[overflow-y:clip] data-closed:animate-collapsible-up data-open:animate-collapsible-down motion-reduce:animate-none',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
@@ -1,46 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { type VariantProps } from 'class-variance-authority'
|
||||
import { ChevronDown } from 'lucide-react'
|
||||
import * as React from 'react'
|
||||
import { alertVariants, Button, cn, Collapsible, CollapsibleContent, CollapsibleTrigger } from 'ui'
|
||||
|
||||
export interface CollapsibleAlertProps
|
||||
extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'>, VariantProps<typeof alertVariants> {
|
||||
trigger: React.ReactNode
|
||||
defaultOpen?: boolean
|
||||
}
|
||||
|
||||
const CollapsibleAlert = React.forwardRef<HTMLDivElement, CollapsibleAlertProps>(
|
||||
({ className, variant = 'default', trigger, defaultOpen, children, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
alertVariants({ variant }),
|
||||
// Override default padding to be tighter
|
||||
'p-3',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<Collapsible defaultOpen={defaultOpen}>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="font-medium">{trigger}</span>
|
||||
<CollapsibleTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="tiny"
|
||||
className={cn('px-0! w-[26px]', '[&[data-state=open]_svg]:rotate-180')}
|
||||
aria-label="Toggle"
|
||||
icon={<ChevronDown className="transition-transform duration-200" />}
|
||||
/>
|
||||
</CollapsibleTrigger>
|
||||
</div>
|
||||
<CollapsibleContent className="pt-3">{children}</CollapsibleContent>
|
||||
</Collapsible>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
CollapsibleAlert.displayName = 'CollapsibleAlert'
|
||||
|
||||
export { CollapsibleAlert }
|
||||
@@ -215,7 +215,7 @@ const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDiv
|
||||
<div
|
||||
data-slot="alert-dialog-footer"
|
||||
className={cn(
|
||||
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 border-t py-3 px-5 [[data-slot=alert-dialog-body]:has(>[role=alert])+&]:border-t-0',
|
||||
'flex flex-col-reverse gap-y-2 sm:flex-row sm:justify-end sm:space-x-2 border-t py-3 px-5 [[data-slot=alert-dialog-body]:has(>[role=alert])+&]:border-t-0',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -373,11 +373,7 @@ const AlertDialogCancel = React.forwardRef<
|
||||
return (
|
||||
<AlertDialogPrimitive.Cancel
|
||||
ref={ref}
|
||||
className={cn(
|
||||
buttonVariants({ variant: 'default', size: 'tiny' }),
|
||||
'mt-2 sm:mt-0',
|
||||
className
|
||||
)}
|
||||
className={cn(buttonVariants({ variant: 'default', size: 'tiny' }), className)}
|
||||
disabled={disabled || alertDialogContext?.loading}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
@@ -153,7 +153,7 @@ const DialogFooter = React.forwardRef<
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
|
||||
'flex flex-col-reverse gap-y-2 sm:flex-row sm:justify-end sm:space-x-2',
|
||||
'border-t',
|
||||
DialogPaddingVariants({ padding }),
|
||||
className
|
||||
@@ -193,11 +193,13 @@ DialogDescription.displayName = DialogPrimitive.Description.displayName
|
||||
const DialogClose = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Close>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Close>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
>(({ className, children, asChild, ...props }, ref) => (
|
||||
<DialogPrimitive.Close
|
||||
ref={ref}
|
||||
asChild={asChild}
|
||||
className={cn(
|
||||
'opacity-70 transition-opacity hover:opacity-100 focus-ring disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-foreground-muted',
|
||||
!asChild &&
|
||||
'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}
|
||||
|
||||
Reference in new issue
Block a user