From 8a0b324dff71d0b273d11bec04ac765e64b8bc1a Mon Sep 17 00:00:00 2001 From: Danny White <3104761+dnywh@users.noreply.github.com> Date: Tue, 21 Jul 2026 10:08:32 -0400 Subject: [PATCH] docs(design-system): add connect interstitials pattern (#45356) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Adds design-system guidance for the shared Connect interstitial layout used by authorisation, invite, marketplace, CLI, and credit flows - Includes a glanceable example showing the centred 400px card for partner authorise and wrong-account invite states - Documents Studio helpers (`InterstitialLayout`, logo helpers, account row, `OrganizationSelector`) so future surfaces reuse one pattern instead of bespoke shells ## Context Most of the Studio Connect UI work from this effort has already landed. This PR keeps the documentation and design-system example so the pattern stays discoverable. Related: [Shared Connect UI for Authorization and Partner Flows](https://linear.app/supabase/project/shared-connect-ui-for-authorization-and-partner-flows-94587ac29d38) ## Test plan - [ ] Open `/docs/ui-patterns/connect-interstitials` in the design system - [ ] Confirm the page appears under UI Patterns in the nav - [ ] Confirm the example renders the authorise and wrong-account cards side by side - [ ] Skim the guidance for accuracy against current Studio `InterstitialLayout` usage ## Summary by CodeRabbit * **New Features** * Added new design-system “Connect Interstitials” example demos, including branding variations (single vs dual logos) and a complete connect-card flow with account row and sign-out action. * Registered the new Connect Interstitials examples in the design-system example registry. * **Documentation** * Added a “Connect Interstitials” UI Patterns page covering when to use the pattern, recommended card/layout structure, branding/logo guidance, and conventions for states, actions, and copy. * Updated the documentation sidebar to include the new page. --- apps/design-system/__registry__/index.tsx | 33 ++++ apps/design-system/config/docs.ts | 5 + .../ui-patterns/connect-interstitials.mdx | 161 ++++++++++++++++++ .../example/connect-interstitial-demo.tsx | 30 ++++ .../connect-interstitial-logo-pair.tsx | 27 +++ .../connect-interstitial-logo-single.tsx | 26 +++ .../example/connect-interstitial-shared.tsx | 125 ++++++++++++++ apps/design-system/registry/examples.ts | 15 ++ 8 files changed, 422 insertions(+) create mode 100644 apps/design-system/content/docs/ui-patterns/connect-interstitials.mdx create mode 100644 apps/design-system/registry/default/example/connect-interstitial-demo.tsx create mode 100644 apps/design-system/registry/default/example/connect-interstitial-logo-pair.tsx create mode 100644 apps/design-system/registry/default/example/connect-interstitial-logo-single.tsx create mode 100644 apps/design-system/registry/default/example/connect-interstitial-shared.tsx diff --git a/apps/design-system/__registry__/index.tsx b/apps/design-system/__registry__/index.tsx index 12c8f4c965b..84d94abdbc4 100644 --- a/apps/design-system/__registry__/index.tsx +++ b/apps/design-system/__registry__/index.tsx @@ -2601,6 +2601,39 @@ export const Index: Record = { subcategory: "undefined", chunks: [] }, + "connect-interstitial-demo": { + name: "connect-interstitial-demo", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/connect-interstitial-demo")), + source: "", + files: ["registry/default/example/connect-interstitial-demo.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "connect-interstitial-logo-pair": { + name: "connect-interstitial-logo-pair", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/connect-interstitial-logo-pair")), + source: "", + files: ["registry/default/example/connect-interstitial-logo-pair.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "connect-interstitial-logo-single": { + name: "connect-interstitial-logo-single", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/connect-interstitial-logo-single")), + source: "", + files: ["registry/default/example/connect-interstitial-logo-single.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, "page-layout-auth-emails": { name: "page-layout-auth-emails", type: "components:example", diff --git a/apps/design-system/config/docs.ts b/apps/design-system/config/docs.ts index 6e72373cf39..df52dd81aa1 100644 --- a/apps/design-system/config/docs.ts +++ b/apps/design-system/config/docs.ts @@ -70,6 +70,11 @@ export const docsConfig: DocsConfig = { href: '/docs/ui-patterns/charts', items: [], }, + { + title: 'Connect Interstitials', + href: '/docs/ui-patterns/connect-interstitials', + items: [], + }, { title: 'Empty States', href: '/docs/ui-patterns/empty-states', diff --git a/apps/design-system/content/docs/ui-patterns/connect-interstitials.mdx b/apps/design-system/content/docs/ui-patterns/connect-interstitials.mdx new file mode 100644 index 00000000000..b5f1514af02 --- /dev/null +++ b/apps/design-system/content/docs/ui-patterns/connect-interstitials.mdx @@ -0,0 +1,161 @@ +--- +title: Connect Interstitials +description: Shared layout guidance for focused authorisation, invite, marketplace, CLI, and credit redemption flows. +--- + +Connect interstitials are focused, single-card flows that sit outside the main +Studio shell. Use the shared `InterstitialLayout` family instead of building +bespoke centered cards, logos, account rows, or organisation selectors. + + + +## Use this pattern for + +This pattern fits short-lived connect flows: partner authorisation and consent +(OAuth, MCP, Stripe Projects), organisation invites, marketplace and billing +connections (AWS Marketplace, Vercel install, credit redemption), and CLI or +device-code sign-in. Use the same shell for their loading, error, success, and +wrong-account states. + +Do not use it for normal authenticated Studio pages. Those should use the +standard [page layout](./layout) patterns. + +## Source of truth + +```tsx +import { OrganizationSelector } from '@/components/interfaces/Connect/OrganizationSelector' +import { + InterstitialAccountRow, + InterstitialLayout, + LogoBox, + LogoPair, + PartnerLogo, + SupabaseLogo, +} from '@/components/layouts/InterstitialLayout' +``` + +## Basic shape + +Use `InterstitialLayout` for the outer card, then put route-specific content in +`px-6 pb-6`. Widen the card only when the flow embeds a real tool, such as +project linking. + +```tsx +} + right={} + /> + } + title="Authorize Stripe Projects" + description="This will create an organization on your behalf in Supabase" +> +
+ + +
+
+``` + +```tsx +} right={} />} + title="Connect Vercel project" + containerClassName="items-start" + cardClassName="max-w-[900px]" +> +
{projectLinker}
+
+``` + +## Logos + +Use `LogoPair` when the user is connecting two services, and `SupabaseLogo` +alone for first-party flows. `PartnerLogo` fills the 48px box edge-to-edge; +`LogoBox` is for custom inset marks or logos that need their own background. +Store new partner icons in `apps/studio/public/img/icons`. + + + + + +```tsx +const AwsLogo = () => ( + + AWS + +) + +} right={} /> +``` + +## Account row + +Use `InterstitialAccountRow` for signed-in context. Do not recreate it locally. + +```tsx + +``` + +## Organisation selection + +Use `OrganizationSelector` when the flow needs an organisation pick. Extend it +for new states instead of inventing a parallel card style. + +```tsx + setShowOrgCreationDialog(true)} +/> +``` + +## Actions + +Prefer one full-width primary action. A full-width text button is fine for a +secondary action that still belongs in the flow. + +## States + +Keep loading, invalid, error, and success states inside the same card when the +route can explain them. Use `ShimmeringLoader` for loading, and `Admonition` +for warning, error, note, and success copy. + +## Copy + +Use sentence case. Prefer `sign in` over `login`. Titles and primary actions +should follow `Verb -> Thing`, for example `Authorize Stripe Projects` or +`Install Vercel`. + +Keep the layout title static across states and put state-specific copy in the +body. Header descriptions should stay short and should not end with a full +stop. diff --git a/apps/design-system/registry/default/example/connect-interstitial-demo.tsx b/apps/design-system/registry/default/example/connect-interstitial-demo.tsx new file mode 100644 index 00000000000..4bee00df445 --- /dev/null +++ b/apps/design-system/registry/default/example/connect-interstitial-demo.tsx @@ -0,0 +1,30 @@ +import { Button } from 'ui' + +import { + AccountRow, + InterstitialShell, + LogoPair, + SignOutButton, + StripeLogo, + SupabaseLogo, +} from './connect-interstitial-shared' + +export default function ConnectInterstitialDemo() { + return ( + } right={} />} + title="Authorize Stripe Projects" + description="This will create an organization on your behalf in Supabase" + > +
+ } /> + + +
+
+ ) +} diff --git a/apps/design-system/registry/default/example/connect-interstitial-logo-pair.tsx b/apps/design-system/registry/default/example/connect-interstitial-logo-pair.tsx new file mode 100644 index 00000000000..a75e0d1f653 --- /dev/null +++ b/apps/design-system/registry/default/example/connect-interstitial-logo-pair.tsx @@ -0,0 +1,27 @@ +import { Button } from 'ui' + +import { + AccountRow, + InterstitialShell, + LogoPair, + SignOutButton, + StripeLogo, + SupabaseLogo, +} from './connect-interstitial-shared' + +export default function ConnectInterstitialLogoPair() { + return ( + } right={} />} + title="Authorize Stripe Projects" + description="This will create an organization on your behalf in Supabase" + > +
+ } /> + +
+
+ ) +} diff --git a/apps/design-system/registry/default/example/connect-interstitial-logo-single.tsx b/apps/design-system/registry/default/example/connect-interstitial-logo-single.tsx new file mode 100644 index 00000000000..7e8fb7b0c5a --- /dev/null +++ b/apps/design-system/registry/default/example/connect-interstitial-logo-single.tsx @@ -0,0 +1,26 @@ +import { Button } from 'ui' +import { Admonition } from 'ui-patterns/admonition' + +import { AccountRow, InterstitialShell, SupabaseLogo } from './connect-interstitial-shared' + +export default function ConnectInterstitialLogoSingle() { + return ( + } + title="Join organization" + description="You have been invited to Acme Labs" + > +
+ + + +
+
+ ) +} diff --git a/apps/design-system/registry/default/example/connect-interstitial-shared.tsx b/apps/design-system/registry/default/example/connect-interstitial-shared.tsx new file mode 100644 index 00000000000..24c35e4da08 --- /dev/null +++ b/apps/design-system/registry/default/example/connect-interstitial-shared.tsx @@ -0,0 +1,125 @@ +import { ArrowRightLeft, LogOut } from 'lucide-react' +import { Avatar, AvatarFallback, Button, Card, CardContent, CardHeader, cn } from 'ui' + +export function LogoBox({ + children, + className, +}: { + children: React.ReactNode + className?: string +}) { + return ( +
+ {children} +
+ ) +} + +export function LogoPair({ left, right }: { left: React.ReactNode; right: React.ReactNode }) { + return ( +
+ {left} + + {right} +
+ ) +} + +export function StripeLogo() { + return ( + + + + + + ) +} + +export function SupabaseLogo() { + return ( + + + + + + + ) +} + +export function AccountRow({ + displayName, + action, +}: { + displayName: string + action?: React.ReactNode +}) { + return ( + + + + A + +
+

Signed in as

+

{displayName}

+
+ {action} +
+
+ ) +} + +export function InterstitialShell({ + logo, + title, + description, + children, +}: { + logo: React.ReactNode + title: string + description?: string + children: React.ReactNode +}) { + return ( +
+ + +
{logo}
+
+

+ {title} +

+ {description ? ( +

+ {description} +

+ ) : null} +
+
+
{children}
+
+
+ ) +} + +export function SignOutButton() { + return