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:
Danny White authored and GitHub committed 2026-10-12 10:18:45 +11:00
1 parent 255a80e219
commit 325a5a0da7
20 files changed
+273 -249

No files matched your search

-5
View File
@@ -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>
)
}
+6 -6
View File
@@ -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>
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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 {
+4 -4
View File
@@ -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}