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
9 changes: 5 additions & 4 deletions apps/design-system/content/docs/components/button.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -125,17 +125,18 @@ Supports slot behavior with `asChild` prop.

Pair a button with a chevron `DropdownMenu` trigger when there are variations of the same action, or alternative ways to accomplish the same goal. The default or most likely option should be used on the exposed button.

When secondary actions are related but distinctnot alternatives to the primary actiondisplay the primary action as a button and place the rest in an overflow menu instead. See [Table multiple actions](./table#multiple-actions).
When secondary actions are related but distinct (not alternatives to the primary action) display the primary action as a button and place the rest in an overflow menu instead. See [Table multiple actions](./table#multiple-actions).

<ComponentPreview name="button-split-dropdown" peekCode />

The shared middle border is the tricky part. Do **not** use `border-l-0` on the chevron button that drops the divider on hover/focus. Instead:
Ensure the middle border is shared rather than doubled-up. Do not use `border-l-0` on the chevron button as that drops the divider on hover/focus. Instead:

- Primary: `rounded-r-none` and `hover:z-10` so its border stacks above the chevron on hover.
- Primary action: `rounded-r-none` and `hover:z-10` so its border stacks above the chevron on hover.
- Chevron trigger: `rounded-l-none`, `shrink-0`, `px-[4px] py-[5px]`, and `-ml-px` to overlap the adjacent border by one pixel.
- Both: `focus-visible:z-10` so the focus ring stacks above the neighbour, and `focus-visible:rounded-r-sm` / `focus-visible:rounded-l-sm` so the squared-off edge is slightly rounded while the ring is shown.
- Chevron trigger only: `aria-label` describing the menu (the icon is decorative).

Inside [Admonition](../fragments/admonition#split-button-with-dropdown) actions, also use `flex w-full @lg:w-auto` with `flex-1 @lg:flex-none` on the primary when `layout="responsive"`.
Inside [Admonition](../fragments/admonition#split-button-with-dropdown) actions when `layout="responsive"`: also use `flex w-full @lg:w-auto` with `flex-1 @lg:flex-none` on the primary action.

## Accessibility

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default function AdmonitionButtonSplitDemo() {
<Button
type="button"
variant="default"
className="flex-1 rounded-r-none px-3 @lg:flex-none hover:z-10"
className="flex-1 rounded-r-none px-3 @lg:flex-none hover:z-10 focus-visible:z-10 focus-visible:rounded-r-sm"
>
Set up SMTP
</Button>
Expand All @@ -30,7 +30,7 @@ export default function AdmonitionButtonSplitDemo() {
type="button"
variant="default"
aria-label="More email template editing options"
className="shrink-0 rounded-l-none px-[4px] py-[5px] -ml-px"
className="shrink-0 rounded-l-none px-[4px] py-[5px] -ml-px focus-visible:z-10 focus-visible:rounded-l-sm"
icon={<ChevronDown />}
/>
</DropdownMenuTrigger>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import {
export default function ButtonSplitDropdownDemo() {
return (
<div className="flex w-fit">
<Button type="button" variant="default" className="rounded-r-none hover:z-10">
<Button
type="button"
variant="default"
className="rounded-r-none hover:z-10 focus-visible:z-10 focus-visible:rounded-r-sm"
>
Primary action
</Button>
<DropdownMenu>
Expand All @@ -20,7 +24,7 @@ export default function ButtonSplitDropdownDemo() {
type="button"
variant="default"
aria-label="More actions"
className="shrink-0 rounded-l-none px-[4px] py-[5px] -ml-px"
className="shrink-0 rounded-l-none px-[4px] py-[5px] -ml-px focus-visible:z-10 focus-visible:rounded-l-sm"
icon={<ChevronDown />}
/>
</DropdownMenuTrigger>
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/content/guides/platform/privatelink.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ Navigate to your project's Integrations section to set up PrivateLink:

Each database, whether the primary or a read replica, needs its own connection. Create a separate connection for every database you want to reach over PrivateLink.

After submission, Supabase creates a VPC Lattice Resource Configuration for your project and sends an AWS Resource Share to the specified AWS account ID. This process may take a few moments. Once complete, the connection will show a "Ready" status, indicating that the resource share has been sent to your AWS account and is ready to be accepted. You must accept the resource share within 12 hours, or the request expires and can no longer be accepted in AWS. You'll need to create a new connection to try again.
After submission, Supabase creates a VPC Lattice Resource Configuration for your project and sends an AWS Resource Share to the specified AWS account ID. This process may take a few moments. Once complete, the connection will show a "Waiting" status, indicating that the resource share has been sent to your AWS account and still needs to be accepted. You must accept the resource share within 12 hours, or the request expires and can no longer be accepted in AWS. You'll need to create a new connection to try again.

Once ready, select **View connection** to see the VPC Lattice resource configuration ID and ARNs for the connection. This is useful for confirming which resource configuration corresponds to which database when a project has multiple connections, for example one for the primary database and one for each read replica.
Select **View connection** to see the VPC Lattice resource configuration ID and ARNs for the connection. This is useful for confirming which resource configuration corresponds to which database when a project has multiple connections, for example one for the primary database and one for each read replica.

### Step 2: Accept resource share

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const ExperimentalTokenDropdown = ({ onCreateToken }: ExperimentalTokenDr
<Button
variant="primary"
aria-label="Choose token scope"
className="-ml-px rounded-l-none px-[4px] py-[5px] focus-visible:z-10"
className="shrink-0 rounded-l-none px-[4px] py-[5px] -ml-px focus-visible:z-10 focus-visible:rounded-l-sm"
icon={<ChevronDown />}
/>
</DropdownMenuTrigger>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const NewTokenButton = ({ onCreateToken }: NewAccessTokenButtonProps) =>
<>
<div className="flex items-center">
<Button
className="rounded-r-none px-3 hover:z-10 focus-visible:z-10"
className="rounded-r-none px-3 hover:z-10 focus-visible:z-10 focus-visible:rounded-r-sm"
onClick={() => setVisible(true)}
>
Generate new token
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,10 @@ export const NewScopedTokenSheet = ({ onCreateExperimentalToken }: NewScopedToke
<Sheet open={isOpen} onOpenChange={handleOpenChange}>
<div className="flex items-center">
<SheetTrigger asChild>
<Button variant="primary" className="rounded-r-none px-3 hover:z-10 focus-visible:z-10">
<Button
variant="primary"
className="rounded-r-none px-3 hover:z-10 focus-visible:z-10 focus-visible:rounded-r-sm"
>
Generate new token
</Button>
</SheetTrigger>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from 'react'

import { useFeaturePreviews } from './useFeaturePreviews'
import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
import { IS_PLATFORM } from '@/lib/constants'
import { EMPTY_OBJ } from '@/lib/void'

Expand Down Expand Up @@ -152,9 +153,18 @@ export const useIsMarketplaceEnabled = () => {
}

export const useIsDatabaseConnectionsEnabled = () => {
const { flags } = useFeaturePreviewContext()
const isDatabaseConnectionsEnabled = useFlag('topForPostgres')
return isDatabaseConnectionsEnabled && flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_DATABASE_CONNECTIONS]
const { flags, isInitialized } = useFeaturePreviewContext()
const [localStorageFlag] = useLocalStorageQuery<boolean | null>(
LOCAL_STORAGE_KEYS.UI_PREVIEW_DATABASE_CONNECTIONS,
null
)
const previouslyToggled = localStorageFlag !== null

return {
enabled: flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_DATABASE_CONNECTIONS],
isInitialized,
previouslyToggled,
}
}

export const useFeaturePreviewModal = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,8 @@ export const useFeaturePreviews = (): FeaturePreview[] => {
discussionsUrl: 'https://github.com/orgs/supabase/discussions/48639',
isNew: true,
isPlatformOnly: false,
isDefaultOptIn: false,
enabled: isDatabaseConnectionsEnabled,
isDefaultOptIn: isDatabaseConnectionsEnabled,
enabled: true,
getRoute: (ref?: string) => `/project/${ref}/observability/connections`,
bannerId: 'database-connections-banner',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const CustomEmailTemplateRestrictionAdmonition = () => {
<Button
asChild
variant="default"
className="flex-1 rounded-r-none px-3 @lg:flex-none hover:z-10 focus-visible:z-10"
className="flex-1 rounded-r-none px-3 @lg:flex-none hover:z-10 focus-visible:z-10 focus-visible:rounded-r-sm"
>
<Link href={`/project/${projectRef}/auth/smtp`}>Set up SMTP</Link>
</Button>
Expand All @@ -37,7 +37,7 @@ export const CustomEmailTemplateRestrictionAdmonition = () => {
<Button
variant="default"
aria-label="More email template editing options"
className="shrink-0 rounded-l-none px-[4px] py-[5px] -ml-px focus-visible:z-10"
className="shrink-0 rounded-l-none px-[4px] py-[5px] -ml-px focus-visible:z-10 focus-visible:rounded-l-sm"
icon={<ChevronDown />}
/>
</DropdownMenuTrigger>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ export const ReplicationPipelineStatus = () => {
<Button
size="tiny"
variant="default"
className="rounded-r-none hover:z-10 focus-visible:z-10"
className="rounded-r-none hover:z-10 focus-visible:z-10 focus-visible:rounded-r-sm"
icon={<RotateCcw />}
disabled={isAnyRestartInProgress || showDisabledState || isPipelineError}
loading={isAnyRestartInProgress}
Expand All @@ -434,8 +434,9 @@ export const ReplicationPipelineStatus = () => {
<DropdownMenuTrigger asChild>
<Button
variant="default"
aria-label="More restart options"
icon={<ChevronDown />}
className="w-7 rounded-l-none -ml-px focus-visible:z-10"
className="shrink-0 rounded-l-none px-[4px] py-[5px] -ml-px focus-visible:z-10 focus-visible:rounded-l-sm"
disabled={showDisabledState || isPipelineError}
/>
</DropdownMenuTrigger>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ export const SchemaGraph = () => {
<div className="flex items-center gap-0">
<ButtonTooltip
variant="default"
className="rounded-r-none border-r-0"
className="rounded-r-none hover:z-10 focus-visible:z-10 focus-visible:rounded-r-sm"
icon={copied ? <Check data-testid="copy-sql-ready" /> : <Copy />}
onClick={copyAsSQL}
tooltip={{
Expand All @@ -458,11 +458,10 @@ export const SchemaGraph = () => {
<Button
variant="default"
size="tiny"
className="rounded-l-none pl-1 pr-0"
aria-label="Export options"
className="shrink-0 rounded-l-none px-[4px] py-[5px] -ml-px focus-visible:z-10 focus-visible:rounded-l-sm"
icon={<ChevronDown size={12} />}
>
<span className="sr-only">Export options</span>
</Button>
/>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-44">
<DropdownMenuItem
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ describe('ExplorerQuerySourceMenu', () => {
) =>
customRender(
<FeatureFlagContext.Provider value={{ configcat: flags, posthog: {}, hasLoaded: true }}>
<ExplorerQuerySourceMenu source={source} onSourceChange={vi.fn()} />
<ExplorerQuerySourceMenu
rowLimit={100}
onRowLimitChange={vi.fn()}
source={source}
onSourceChange={vi.fn()}
/>
</FeatureFlagContext.Provider>
)

Expand All @@ -53,6 +58,8 @@ describe('ExplorerQuerySourceMenu', () => {

customRender(
<ExplorerQuerySourceMenu
rowLimit={100}
onRowLimitChange={vi.fn()}
source={{
_tag: 'logs',
time_range: { _tag: 'relative_time_range', amount: 1, unit: 'hour' },
Expand All @@ -72,6 +79,8 @@ describe('ExplorerQuerySourceMenu', () => {

customRender(
<ExplorerQuerySourceMenu
rowLimit={100}
onRowLimitChange={vi.fn()}
source={{
_tag: 'logs',
time_range: { _tag: 'relative_time_range', amount: 1, unit: 'hour' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
DropdownMenuTrigger,
} from 'ui'

import { RowLimitSubMenu } from '../SQLEditor/UtilityPanel/QuerySourceMenu/RowLimitSubMenu'
import { RunAsSubMenu } from '../SQLEditor/UtilityPanel/QuerySourceMenu/RunAsSubMenu'
import { DatabaseParametersSubMenu } from '@/components/interfaces/QuerySources/DatabaseParametersSubMenu'
import { LogsCustomRangeDialog } from '@/components/interfaces/QuerySources/LogsCustomRangeDialog'
import { LogsTimeRangeSubMenu } from '@/components/interfaces/QuerySources/LogsTimeRangeSubMenu'
Expand All @@ -21,8 +23,12 @@ import {
QUERY_SOURCES,
type QuerySourceBinding,
} from '@/data/query-sources/query-source-registry'
import { type RoleImpersonationController } from '@/state/role-impersonation-state'

export type ExplorerQuerySourceMenuProps = {
rowLimit?: number
onRowLimitChange?: (val: number) => void
roleImpersonationState?: RoleImpersonationController
source: QuerySourceBinding
onSourceChange: (source: QuerySourceBinding) => void
}
Expand All @@ -37,6 +43,9 @@ export type ExplorerQuerySourceMenuProps = {
* has SQL to preserve or discard and a fresh draft does not.
*/
export const ExplorerQuerySourceMenu = ({
rowLimit = 100,
onRowLimitChange,
roleImpersonationState,
source,
onSourceChange,
}: ExplorerQuerySourceMenuProps) => {
Expand Down Expand Up @@ -97,12 +106,23 @@ export const ExplorerQuerySourceMenu = ({
<DropdownMenuSeparator />

{source._tag === 'database' ? (
<DatabaseParametersSubMenu
identifier={source.database_identifier ?? ref}
onIdentifierChange={(database_identifier) =>
onSourceChange({ _tag: 'database', database_identifier })
}
/>
<>
<DatabaseParametersSubMenu
identifier={source.database_identifier ?? ref}
onIdentifierChange={(database_identifier) =>
onSourceChange({ _tag: 'database', database_identifier })
}
/>
{roleImpersonationState !== undefined && (
<RunAsSubMenu controlled state={roleImpersonationState} />
)}
{onRowLimitChange !== undefined && (
<RowLimitSubMenu
value={rowLimit}
onValueChange={(val) => onRowLimitChange(Number(val))}
/>
)}
</>
) : (
<LogsTimeRangeSubMenu
range={source.time_range}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
cloneQueryCell,
DEFAULT_CELL_ROW_LIMIT,
getCellDisplay,
setCellRowLimit,
setCellSql,
toQueryModel,
} from './QueryCell.utils'
Expand Down Expand Up @@ -122,6 +123,16 @@ describe('setCellSql', () => {
})
})

describe('setCellRowLimit', () => {
it('writes the row limit onto a database cell without touching its query', () => {
expect(setCellRowLimit(DATABASE_CELL, 500)).toEqual({ ...DATABASE_CELL, row_limit: 500 })
})

it('leaves a log cell unchanged, since it has no row limit concept', () => {
expect(setCellRowLimit(LOG_CELL, 500)).toEqual(LOG_CELL)
})
})

describe('cloneQueryCell', () => {
it('copies the chart series array rather than aliasing it', () => {
const clone = cloneQueryCell(DATABASE_CELL)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,22 @@ export function setCellSql(cell: Snapshot<QueryCell>, sql: string): QueryCell {
}
}

/**
* Writes a new row limit onto a database cell. A log cell has no row limit concept, so it
* passes through unchanged.
*/
export function setCellRowLimit(cell: Snapshot<QueryCell>, rowLimit: number): QueryCell {
if (cell._tag === 'log_cell') return cloneQueryCell(cell)

return {
...copyQueryCellBase(cell),
_tag: 'database_cell',
unchecked_sql: cell.unchecked_sql,
row_limit: rowLimit,
database_identifier: cell.database_identifier,
}
}

/**
* Builds the editor's query model from a cell and the editor's live text buffer. Branding
* the buffer is the editor boundary the safe-SQL model expects; which brand applies is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
cloneChartConfig,
cloneQueryCell,
getCellDisplay,
setCellRowLimit,
setCellSql,
toQueryModel,
} from './QueryCell.utils'
Expand All @@ -20,6 +21,7 @@ import {
} from '@/data/content/notebooks/notebook-schema'
import { type QuerySourceBinding } from '@/data/query-sources/query-source-registry'
import { useCurrentNotebook, useNotebooksStateSnapshot } from '@/state/notebooks/notebooks-state'
import { useLocalRoleImpersonationState } from '@/state/role-impersonation-state'

interface QueryCellProps {
cell: Snapshot<QueryCellSchema>
Expand All @@ -32,6 +34,7 @@ export const QueryCell = ({ cell }: QueryCellProps) => {

const [sql, setSql] = useState<string>(cell.unchecked_sql)
const [result, setResult] = useState<QueryResult>()
const roleImpersonationState = useLocalRoleImpersonationState()

const title = cell.title ?? 'Untitled query'

Expand Down Expand Up @@ -78,6 +81,9 @@ export const QueryCell = ({ cell }: QueryCellProps) => {
chart: cloneChartConfig(display.chart),
}))

const handleRowLimitChange = (rowLimit: number) =>
updateQueryCell((candidate) => setCellRowLimit(candidate, rowLimit))

return (
<SortableSection
id={cell.id}
Expand All @@ -91,12 +97,14 @@ export const QueryCell = ({ cell }: QueryCellProps) => {
title={title}
query={toQueryModel(cell, sql)}
result={result}
roleImpersonationState={roleImpersonationState}
display={getCellDisplay(cell)}
onTitleChange={handleTitleChange}
onSqlChange={setSql}
onSqlCommit={handleSqlCommit}
onSourceChange={handleSourceChange}
onResultChange={setResult}
onRowLimitChange={handleRowLimitChange}
onDisplayChange={handleDisplayChange}
/>
</SortableSection>
Expand Down
Loading
Loading