Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions apps/design-system/__registry__/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ import * as React from "react"

export const Index: Record<string, any> = {
"default": {
"CollapsibleCardSection": {
name: "CollapsibleCardSection",
type: "components:fragment",
registryDependencies: undefined,
component: React.lazy(() => import("@/../../packages/ui-patterns/src/CollapsibleCardSection")),
source: "",
files: ["registry/default//CollapsibleCardSection.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"ConfirmationModal": {
name: "ConfirmationModal",
type: "components:fragment",
Expand Down Expand Up @@ -225,6 +236,17 @@ export const Index: Record<string, any> = {
subcategory: "undefined",
chunks: []
},
"collapsible-card-section-demo": {
name: "collapsible-card-section-demo",
type: "components:example",
registryDependencies: ["collapsible-card-section"],
component: React.lazy(() => import("@/registry/default/example/collapsible-card-section-demo")),
source: "",
files: ["registry/default/example/collapsible-card-section-demo.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"alert-dialog-demo": {
name: "alert-dialog-demo",
type: "components:example",
Expand Down
5 changes: 5 additions & 0 deletions apps/design-system/config/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ export const docsConfig: DocsConfig = {
href: '/docs/fragments/collapsible-alert',
items: [],
},
{
title: 'Collapsible Card Section',
href: '/docs/fragments/collapsible-card-section',
items: [],
},
{
title: 'Assistant Chat',
href: '/docs/fragments/assistant-chat',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
title: Collapsible Card Section
description: A mono-uppercase collapsible trigger for hiding optional or advanced settings inside a card or panel.
fragment: true
---

Use `CollapsibleCardSection` to progressively disclose optional or advanced fields inside a card or panel form. The trigger is styled mono-uppercase to blend with card section headings; content animates in and out on toggle.

<ComponentPreview name="collapsible-card-section-demo" peekCode wide />

## Usage

```tsx
import { CollapsibleCardSection } from 'ui-patterns/CollapsibleCardSection'
```

```tsx
<CollapsibleCardSection title="Advanced settings">{/* form fields */}</CollapsibleCardSection>
```

### With description

Use `description` to add a short qualifier below the title, only when the content needs context that the title alone doesn't provide.

```tsx
<CollapsibleCardSection
title="Advanced settings"
description="These settings cannot be changed after creation"
>
{/* form fields */}
</CollapsibleCardSection>
```

### Default open

```tsx
<CollapsibleCardSection title="Advanced settings" defaultOpen>
{/* form fields */}
</CollapsibleCardSection>
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Input } from 'ui'
import { CollapsibleCardSection } from 'ui-patterns/CollapsibleCardSection'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'

export default function CollapsibleCardSectionDemo() {
return (
<div className="border rounded-lg px-6 py-4 w-full max-w-lg">
<CollapsibleCardSection
title="Advanced settings"
description="These settings cannot be changed after creation"
>
<FormItemLayout
isReactForm={false}
layout="flex-row-reverse"
label="OIDC Issuer"
description="The OIDC issuer URL of your identity provider."
>
<Input placeholder="https://your-org.okta.com" />
</FormItemLayout>
</CollapsibleCardSection>
</div>
)
}
6 changes: 6 additions & 0 deletions apps/design-system/registry/examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ export const examples: Registry = [
registryDependencies: ['collapsible-alert'],
files: ['example/collapsible-alert-demo.tsx'],
},
{
name: 'collapsible-card-section-demo',
type: 'components:example',
registryDependencies: ['collapsible-card-section'],
files: ['example/collapsible-card-section-demo.tsx'],
},
{
name: 'alert-dialog-demo',
type: 'components:example',
Expand Down
6 changes: 6 additions & 0 deletions apps/design-system/registry/fragments.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { Registry } from '@/registry/schema'

export const fragments: Registry = [
{
name: 'CollapsibleCardSection',
type: 'components:fragment',
files: ['/CollapsibleCardSection.tsx'],
optionalPath: '',
},
{
name: 'ConfirmationModal',
type: 'components:fragment',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ DROP FUNCTION <function name>() CASCADE;
4. Recreate the triggers

**Example function and trigger using security definer**
The [SQL Editor](/dashboard/project/_/sql/) contains a template for [User Management](/dashboard/project/_/sql/quickstarts). Within it, there is a working example of how to setup triggers with security definer that may be worth referencing:
The [SQL Editor](/dashboard/project/_/sql/) contains a template for [User Management](/dashboard/project/_/sql/examples). Within it, there is a working example of how to setup triggers with security definer that may be worth referencing:

```sql
create table profiles (
Expand Down
2 changes: 2 additions & 0 deletions apps/studio/.env
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ SUPABASE_URL=http://localhost:8000
SUPABASE_PUBLIC_URL=http://localhost:8000
SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE
SUPABASE_SERVICE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q
SUPABASE_PUBLISHABLE_KEY=
SUPABASE_SECRET_KEY=
SENTRY_IGNORE_API_RESOLUTION_ERROR=1
LOGFLARE_URL=http://localhost:4000
LOGFLARE_PRIVATE_ACCESS_TOKEN=your-super-secret-and-long-logflare-key-private
Expand Down
6 changes: 3 additions & 3 deletions apps/studio/.github/eslint-rule-baselines.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@
"pages/project/[ref]/auth/policies.tsx": 2,
"pages/project/[ref]/editor/[id].tsx": 1,
"pages/project/[ref]/settings/jwt/legacy.tsx": 1,
"pages/project/[ref]/sql/quickstarts.tsx": 1,
"pages/project/[ref]/sql/examples.tsx": 1,
"pages/project/[ref]/sql/templates.tsx": 1,
"pages/sign-in-mfa.tsx": 1,
"state/role-impersonation-state.tsx": 1,
Expand Down Expand Up @@ -363,7 +363,7 @@
"components/interfaces/SQLEditor/RenameQueryModal.tsx": 2,
"components/interfaces/SQLEditor/SQLEditor.tsx": 4,
"components/interfaces/SQLEditor/SQLEditor.utils.ts": 1,
"components/interfaces/SQLEditor/SQLTemplates/SQLQuickstarts.tsx": 1,
"components/interfaces/SQLEditor/SQLTemplates/SQLExamples.tsx": 1,
"components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx": 1,
"components/interfaces/SQLEditor/UtilityPanel/CellDetailPanel.tsx": 1,
"components/interfaces/SQLEditor/UtilityPanel/ChartConfig.tsx": 2,
Expand Down Expand Up @@ -755,7 +755,7 @@
"components/interfaces/SQLEditor/InlineWidget.tsx": 1,
"components/interfaces/SQLEditor/MonacoEditor.tsx": 1,
"components/interfaces/SQLEditor/RenameQueryModal.tsx": 1,
"components/interfaces/SQLEditor/SQLTemplates/SQLQuickstarts.tsx": 1,
"components/interfaces/SQLEditor/SQLTemplates/SQLExamples.tsx": 1,
"components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx": 1,
"components/interfaces/SQLEditor/UtilityPanel/ReadOnlyBadge.tsx": 1,
"components/interfaces/SQLEditor/UtilityPanel/Results.tsx": 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ export const TableList = ({
{!isSchemaLocked && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button type="default" className="px-1" icon={<MoreVertical />} />
<Button type="text" className="px-1" icon={<MoreVertical />} />
</DropdownMenuTrigger>
<DropdownMenuContent side="bottom" align="end" className="w-40">
<DropdownMenuItem
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,7 @@ const WRAPPER_INTEGRATIONS: Array<IntegrationDefinition> = WRAPPERS.map((w) => {
requiredExtensions: ['wrappers', 'supabase_vault'],
description: w.description,
docsUrl: w.docsUrl,
categories: w.categories,
meta: w,
author: authorSupabase,
navigation: [
Expand Down Expand Up @@ -640,12 +641,35 @@ const TEMPLATE_INTEGRATIONS: Array<IntegrationDefinition> = [
},
]

export const INTEGRATIONS: Array<IntegrationDefinition> = [
const INTEGRATIONS_WITH_CATEGORIES = [
...WRAPPER_INTEGRATIONS,
...SUPABASE_INTEGRATIONS,
...TEMPLATE_INTEGRATIONS,
...SUPABASE_INTEGRATIONS.map((integration) => {
const categoryMap: Record<string, string[]> = {
queues: ['devtools'],
cron: ['devtools'],
vault: ['security'],
webhooks: ['api'],
data_api: ['api', 'data-platform'],
graphiql: ['api', 'devtools'],
}
return {
...integration,
categories: categoryMap[integration.id] || [],
}
}),
...TEMPLATE_INTEGRATIONS.map((integration) => {
const categoryMap: Record<string, string[]> = {
stripe_sync_engine: ['billing'],
}
return {
...integration,
categories: categoryMap[integration.id] || [],
}
}),
]

export const INTEGRATIONS: Array<IntegrationDefinition> = INTEGRATIONS_WITH_CATEGORIES

export const Loading = () => (
<div className="p-10">
<GenericSkeletonLoader />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { useMemo } from 'react'

import type { IntegrationDefinition } from './Integrations.constants'

interface UseIntegrationFilteringAndSortOptions {
featuredIds: readonly string[]
hasActiveFilter: boolean
includeFeaturedFlag?: boolean
}

// Filters all available integrations first by category,
// then by the search term and sorts them first by
// installation status and then alphabetically
export const useIntegrationFilteringAndSort = (
filteredIntegrations: readonly IntegrationDefinition[],
availableIntegrations: readonly IntegrationDefinition[],
installedIds: readonly string[],
options: UseIntegrationFilteringAndSortOptions
) => {
const sorted = useMemo(() => {
return sortByInstalledThenName(filteredIntegrations, installedIds)
}, [filteredIntegrations, installedIds])

const featured = useMemo(() => {
if (options.hasActiveFilter) return []

const byId = new Map(availableIntegrations.map((i) => [i.id, i]))

// Pinned IDs preserve their explicit order
const pinnedResult = options.featuredIds
.map((id) => byId.get(id))
.filter((i): i is IntegrationDefinition => !!i)

// For legacy: also include integrations with featured flag, sorted among themselves
if (options.includeFeaturedFlag) {
const flaggedFeatured = availableIntegrations.filter(
(i) => i.featured && !options.featuredIds.includes(i.id)
)
pinnedResult.push(...sortByInstalledThenName(flaggedFeatured, installedIds))
}

return pinnedResult
}, [
availableIntegrations,
options.hasActiveFilter,
options.featuredIds,
options.includeFeaturedFlag,
installedIds,
])

return { sorted, featured }
}

/**
* Sort integrations by installation status (installed first) then alphabetically by name.
*/
export function sortByInstalledThenName(
integrations: readonly IntegrationDefinition[],
installedIds: readonly string[]
): IntegrationDefinition[] {
return [...integrations].sort((a, b) => {
const aIsInstalled = installedIds.includes(a.id)
const bIsInstalled = installedIds.includes(b.id)

if (aIsInstalled && !bIsInstalled) return -1
if (!aIsInstalled && bIsInstalled) return 1

return a.name.localeCompare(b.name)
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,14 @@ import type {

export type { MarketplaceSource } from '@/components/interfaces/Integrations/Landing/Integrations.constants'

// Launch-partner pin list for the featured rail. Order here is the order
// shown. Stripe is the only first-party template in the set (id matches the
// static catalogue); the rest are marketplaceDB slugs and only render if the
// listing is published.
// Defines featured integrations and their order in the featured hero
export const FEATURED_INTEGRATION_IDS = [
'grafana',
'stripe_sync_engine',
'aikido',
'aikido-security',
'doppler',
'cipherstash',
'resend',
] as const

export type MarketplaceIntegrationType = 'oauth' | 'postgres_extension' | 'template' | 'wrapper'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,7 @@ export const MarketplaceCard = ({ integration, isInstalled }: MarketplaceCardPro
<div className="flex items-center justify-between gap-2 pt-2.5">
<div className="flex flex-wrap items-center gap-1">
<MarketplaceSourceBadge source={source} />
{integration.status && (
<Badge variant="warning" className="capitalize">
{integration.status}
</Badge>
)}
{integration.status && <Badge variant="warning">{integration.status}</Badge>}
</div>
<div className="text-xs flex items-center gap-1 text-foreground-lighter">
<span>Built by</span>
Expand Down
Loading
Loading