mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 01:45:10 +03:00
## What kind of change does this PR introduce? Refactor. Follow-up to #49871. ## What is the current behavior? Branded (green) text still uses the `text-brand` classname while the colour comes from `--primary`. ## What is the new behavior? **Rename-only:** `text-brand` → `text-primary` across callsites and docs. Leftover `bg-brand` / `border-brand` alias to `brand-default`. No intentional colour changes in this PR. This better matches how we treat our green in other components and props, like `variant="primary"` for green buttons. ## To test On light mode: smoke-test that branded text still looks like #49871 (readable green, not the bright fill). - [Homepage](https://zone-www-dot-com-git-dnywh-depr-316-text-brand-de2380-supabase.vercel.app/): “Scale to millions” uses `text-primary` - [Docs homepage](https://docs-git-dnywh-depr-316-text-brand-to-primary-supabase.vercel.app/docs): branded links like “More on self-hosting” are still readable - [Typography docs](https://design-system-git-dnywh-depr-316-text-brand-to-primary-supabase.vercel.app/design-system/docs/typography): documents `text-primary` - [Colour usage](https://design-system-git-dnywh-depr-316-text-brand-to-primary-supabase.vercel.app/design-system/docs/color-usage): `text-primary` is visibly darker than `bg-brand-default` _on light mode_ - [Studio auth providers](https://studio-staging-git-dnywh-depr-316-text-brand-to-60fa6c-supabase.vercel.app/dashboard/project/_/auth/providers): enabled provider badge text readable; status dot stays bright green <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Style** * Updated branded text, link hover states, icons, badges, indicators, and highlighted content across the Design System, Docs, Studio, Learn, UI Library, and marketing experiences to use the primary theme color. * Updated syntax highlighting and table-of-contents styling for consistent primary-color presentation. * Refined brand color fallback behavior for bright fills and borders. * **Documentation** * Updated color-usage and typography guidance to recommend the primary text utility. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
180 lines
5.3 KiB
TypeScript
180 lines
5.3 KiB
TypeScript
import { Check, X } from 'lucide-react'
|
|
import { type PropsWithChildren, type ReactNode } from 'react'
|
|
import { Button, cn } from 'ui'
|
|
|
|
import { getConfirmFooterBar, type ConfirmFooterApprovalState } from './Confirm.utils'
|
|
|
|
interface ConfirmFooterProps {
|
|
message: string
|
|
cancelLabel?: string
|
|
confirmLabel?: string
|
|
confirmLabelLoading?: string
|
|
isLoading?: boolean
|
|
isDisabled?: boolean
|
|
outcome?: 'success' | 'error' | 'denied'
|
|
showActions?: boolean
|
|
action?: ReactNode
|
|
/** Omit the confirm button so only Skip remains (unparseable / unapplyable previews). */
|
|
denyOnly?: boolean
|
|
/** Escape hatch for consumers that attach the bar directly under their own frame. */
|
|
className?: string
|
|
onCancel?: () => void | Promise<void>
|
|
onConfirm?: () => void | Promise<void>
|
|
}
|
|
|
|
/** Action bar that sits at the bottom of `Confirm`. */
|
|
export const ConfirmFooter = ({
|
|
message,
|
|
cancelLabel = 'Cancel',
|
|
confirmLabel = 'Confirm',
|
|
confirmLabelLoading = 'Working...',
|
|
isLoading = false,
|
|
isDisabled = false,
|
|
outcome,
|
|
showActions = true,
|
|
action,
|
|
denyOnly = false,
|
|
className,
|
|
onCancel,
|
|
onConfirm,
|
|
}: ConfirmFooterProps) => {
|
|
const isInactive = isLoading || isDisabled
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'mx-3 flex shrink-0 items-center justify-between gap-3 rounded-b-md py-2 pr-2 pl-3',
|
|
'border-x border-b bg-surface-200 text-xs text-foreground-light',
|
|
className
|
|
)}
|
|
>
|
|
<div role="status" className="min-w-0 flex flex-1 items-center gap-2">
|
|
{outcome === 'success' && <Check className="size-3.5 shrink-0 text-primary" />}
|
|
{outcome === 'error' && <X className="size-3.5 shrink-0 text-danger" />}
|
|
<span>{message}</span>
|
|
</div>
|
|
{(showActions || action) && (
|
|
<div className="flex shrink-0 items-center gap-2">
|
|
{showActions && (
|
|
<>
|
|
<Button size="tiny" variant="outline" onClick={onCancel} disabled={isInactive}>
|
|
{cancelLabel}
|
|
</Button>
|
|
{!denyOnly && (
|
|
<Button
|
|
size="tiny"
|
|
variant="primary"
|
|
onClick={onConfirm}
|
|
loading={isLoading}
|
|
disabled={isInactive}
|
|
>
|
|
{isLoading ? confirmLabelLoading : confirmLabel}
|
|
</Button>
|
|
)}
|
|
</>
|
|
)}
|
|
{action}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
interface ConfirmProps {
|
|
/**
|
|
* Result of `getManualToolApprovalConfirmState`. Interactive buttons only for
|
|
* `approval-requested`; `approval-responded` is the post-approve loading morph.
|
|
*/
|
|
state?: ConfirmFooterApprovalState
|
|
message: string
|
|
cancelLabel?: string
|
|
confirmLabel?: string
|
|
confirmLabelLoading?: string
|
|
successMessage?: string
|
|
errorMessage?: string
|
|
deniedMessage?: string
|
|
footerAction?: ReactNode
|
|
extraLoading?: boolean
|
|
isLoading?: boolean
|
|
/**
|
|
* Children fill the remaining height of the card (e.g. `QueryEditor` in viewport
|
|
* mode). Omit for content-sized bodies like notebook previews and Edge Function blocks.
|
|
*/
|
|
fill?: boolean
|
|
className?: string
|
|
/** Omit the confirm button so only Skip remains. */
|
|
denyOnly?: boolean
|
|
onCancel?: () => void | Promise<void>
|
|
onConfirm?: () => void | Promise<void>
|
|
}
|
|
|
|
/**
|
|
* Card that wraps an assistant tool preview and optionally attaches a confirm footer
|
|
* below it. The card owns the frame; the footer hangs inset from the sides so it reads
|
|
* as tucked under the card. Nested surfaces (QueryEditor viewport, unframed notebook
|
|
* previews, Edge Function blocks) fill the body.
|
|
*/
|
|
export const Confirm = ({
|
|
children,
|
|
state,
|
|
message,
|
|
cancelLabel = 'Skip',
|
|
confirmLabel = 'Confirm',
|
|
confirmLabelLoading = 'Working...',
|
|
successMessage,
|
|
errorMessage,
|
|
deniedMessage,
|
|
footerAction,
|
|
extraLoading = false,
|
|
isLoading = false,
|
|
fill = false,
|
|
className,
|
|
denyOnly = false,
|
|
onCancel,
|
|
onConfirm,
|
|
}: PropsWithChildren<ConfirmProps>) => {
|
|
const bar = getConfirmFooterBar(state)
|
|
const showLoading = bar.isLoading || extraLoading || isLoading
|
|
const isApprovalRequested = state === 'approval-requested'
|
|
const isApprovalResponded = state === 'approval-responded'
|
|
const showActions = isApprovalRequested || isApprovalResponded
|
|
const outcomeMessages = {
|
|
success: successMessage,
|
|
error: errorMessage,
|
|
denied: deniedMessage,
|
|
}
|
|
const footerMessage = bar.outcome ? (outcomeMessages[bar.outcome] ?? message) : message
|
|
|
|
return (
|
|
<div
|
|
data-slot="assistant-confirm"
|
|
className={cn('flex flex-col', fill && 'min-h-64', className)}
|
|
>
|
|
<div
|
|
className={cn(
|
|
'min-w-0 overflow-hidden rounded-md border shadow-xs bg-muted',
|
|
fill && 'flex min-h-0 flex-1 flex-col overflow-hidden'
|
|
)}
|
|
>
|
|
{children}
|
|
</div>
|
|
{bar.show && (
|
|
<ConfirmFooter
|
|
message={footerMessage}
|
|
cancelLabel={cancelLabel}
|
|
confirmLabel={confirmLabel}
|
|
confirmLabelLoading={confirmLabelLoading}
|
|
isLoading={showLoading}
|
|
isDisabled={!isApprovalRequested}
|
|
outcome={bar.outcome}
|
|
showActions={showActions}
|
|
action={footerAction}
|
|
denyOnly={denyOnly}
|
|
onCancel={onCancel}
|
|
onConfirm={onConfirm}
|
|
/>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|