mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 01:15:03 +03:00
feat(docs): agent prompt block (#50565)
## What kind of change does this PR introduce? docs ui polish for the prompt panel on the docs homepage and in the quickstarts ## What is the current behavior? agent prompt renders as one run on paragraph steps read as "1. ... 2. ... 3. ..." inline making it hard to read ## What is the new behavior? - sets prompt bodies as markdown for easier readability - adds long prompts collapse - removes panel tab icons - uses panel code block ui - makes active tab has an underline on first paint `home` | state | preview | | -------|------| | before | <img width="709" height="344" alt="image" src="https://github.com/user-attachments/assets/ba3a0948-efff-4144-8eba-9065e1f140d9" /> | | after | <img width="709" height="344" alt="image" src="https://github.com/user-attachments/assets/e63e71e9-86d2-4d9a-ac2d-925e197d69f9" /> | `quickstart` | state | preview | | -------|------| | before | <img width="862" height="344" alt="image" src="https://github.com/user-attachments/assets/96faf012-a9f1-4ee9-8a98-3ec6de24d2de" /> | | after | <img width="862" height="344" alt="image" src="https://github.com/user-attachments/assets/7a41a259-e181-4a32-949f-de095d8bb66c" /> | <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Prompts now support Markdown formatting, inline code chips, expandable content, and improved hover-reveal behavior. * Code-copy controls support customizable labels and clearer screen-reader announcements. * Prompt tabs and panels have updated styling, spacing, and active-content handling. * **UI Improvements** * Setup and agent prompts now use consistent Markdown-rendered presentation. * Prompt headings now display “Agent Prompt.” * Home page setup code blocks use a more compact layout. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
986931184c
commit
5918a69398
9 files changed
+140
-115
No files matched your search
@@ -6,4 +6,8 @@ export const setupCommand = {
|
||||
|
||||
export const setupCommands = [setupCommand.installCli, setupCommand.installPlugin].join('\n')
|
||||
|
||||
export const setupPrompt = `Help me get set up with Supabase. Do the following: 1. Install the Supabase CLI as a project dev dependency with \`${setupCommand.installCli}\`, so the version is pinned per project. 2. Install the Supabase Plugin with \`${setupCommand.installPlugin}\`. 3. Review my project and determine whether Supabase is already initialized. If it is not initialized, run \`${setupCommand.initialize}\`. 4. Suggest the most relevant next steps.`
|
||||
export const setupPrompt = `Help me get set up with Supabase. Do the following:
|
||||
1. Install the Supabase CLI as a project dev dependency with \`${setupCommand.installCli}\`, so the version is pinned per project.
|
||||
2. Install the Supabase Plugin with \`${setupCommand.installPlugin}\`.
|
||||
3. Review my project and determine whether Supabase is already initialized. If it is not initialized, run \`${setupCommand.initialize}\`.
|
||||
4. Suggest the most relevant next steps.`
|
||||
@@ -4,45 +4,31 @@ import {
|
||||
Prompt,
|
||||
PromptContent,
|
||||
PromptCopy,
|
||||
PromptMarkdown,
|
||||
PromptPanel,
|
||||
PromptTitle,
|
||||
} from '~/features/ui/PromptPanel'
|
||||
import { isFeatureEnabled } from 'common'
|
||||
import { Sparkles, Terminal } from 'lucide-react'
|
||||
import { type ReactNode } from 'react'
|
||||
|
||||
import { getCustomContent } from '../lib/custom-content/getCustomContent'
|
||||
import DocsCoverLogo from './DocsCoverLogo'
|
||||
import { setupCommand, setupCommands, setupPrompt } from './HomePageCover.constants'
|
||||
import { setupCommands, setupPrompt } from './HomePageCover.constants'
|
||||
|
||||
const fullGettingStartedEnabled = isFeatureEnabled('docs:full_getting_started')
|
||||
|
||||
function SetupPrompt({ cliCode }: { cliCode: ReactNode }) {
|
||||
return (
|
||||
<PromptPanel>
|
||||
<Prompt value="prompt">
|
||||
<PromptTitle icon={<Sparkles />}>AI Prompt</PromptTitle>
|
||||
<Prompt value="prompt" expandable>
|
||||
<PromptTitle>Agent Prompt</PromptTitle>
|
||||
<PromptCopy>{setupPrompt}</PromptCopy>
|
||||
<PromptContent>
|
||||
Help me get set up with Supabase. Do the following: 1. Install the Supabase CLI as a
|
||||
project dev dependency with{' '}
|
||||
<code className="shimmer-none rounded bg-surface-200 px-1 py-0.5 font-mono text-xs text-foreground">
|
||||
{setupCommand.installCli}
|
||||
</code>
|
||||
, so the version is pinned per project. 2. Install the Supabase Plugin with{' '}
|
||||
<code className="shimmer-none rounded bg-surface-200 px-1 py-0.5 font-mono text-xs text-foreground">
|
||||
{setupCommand.installPlugin}
|
||||
</code>
|
||||
. 3. Review my project and determine whether Supabase is already initialized. If it is not
|
||||
initialized, run{' '}
|
||||
<code className="shimmer-none rounded bg-surface-200 px-1 py-0.5 font-mono text-xs text-foreground">
|
||||
{setupCommand.initialize}
|
||||
</code>
|
||||
. 4. Suggest the most relevant next steps.
|
||||
<PromptMarkdown>{setupPrompt}</PromptMarkdown>
|
||||
</PromptContent>
|
||||
</Prompt>
|
||||
<Prompt value="cli">
|
||||
<PromptTitle icon={<Terminal />}>CLI</PromptTitle>
|
||||
<PromptTitle>CLI</PromptTitle>
|
||||
<PromptCopy>{setupCommands}</PromptCopy>
|
||||
<PromptContent shimmer={false}>{cliCode}</PromptContent>
|
||||
</Prompt>
|
||||
@@ -56,8 +42,8 @@ const HomePageCover = ({ title, cliCode }: { title: string; cliCode: ReactNode }
|
||||
return (
|
||||
<div className="w-full border-b bg-muted/10">
|
||||
<div className="mx-auto max-w-7xl px-6 py-16">
|
||||
<div className="flex flex-col gap-10 lg:flex-row lg:items-center lg:gap-12 xl:gap-16">
|
||||
<div className="flex w-full min-w-0 flex-1 items-center gap-4 sm:gap-8">
|
||||
<div className="flex flex-col gap-10 lg:flex-row lg:items-start lg:gap-12 xl:gap-16">
|
||||
<div className="flex w-full min-w-0 flex-1 items-center gap-4 sm:gap-8 mt-10">
|
||||
<DocsCoverLogo aria-hidden="true" className="w-12 shrink-0 sm:w-[60px] md:w-[100px]" />
|
||||
<div className="flex min-w-0 flex-col">
|
||||
<h1 className="m-0 text-3xl text-foreground sm:text-4xl">
|
||||
|
||||
@@ -12,6 +12,7 @@ import ReactMarkdown from 'react-markdown'
|
||||
import { ConnectionIcon } from 'ui-patterns/McpUrlBuilder'
|
||||
|
||||
import { AiPrompt } from './AiPrompt'
|
||||
import { PromptCode } from './PromptPanel'
|
||||
import { TabPanel, Tabs } from './Tabs'
|
||||
|
||||
type AgentSetupProps = {
|
||||
@@ -33,11 +34,7 @@ const markdownComponents = {
|
||||
</a>
|
||||
)
|
||||
},
|
||||
code: ({ children }: { children?: ReactNode }) => (
|
||||
<code className="rounded bg-surface-200 px-1 py-0.5 font-mono text-xs text-foreground">
|
||||
{children}
|
||||
</code>
|
||||
),
|
||||
code: PromptCode,
|
||||
}
|
||||
|
||||
function HarnessBody({ harness }: { harness: MonitoringAgentHarnessSetup }) {
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
'use client'
|
||||
|
||||
import { aiPrompts, type AiPromptId } from '~/data/ai-prompts.data'
|
||||
import { Sparkles } from 'lucide-react'
|
||||
|
||||
import { Prompt, PromptContent, PromptCopy, PromptPanel, PromptTitle } from './PromptPanel'
|
||||
import {
|
||||
Prompt,
|
||||
PromptContent,
|
||||
PromptCopy,
|
||||
PromptMarkdown,
|
||||
PromptPanel,
|
||||
PromptTitle,
|
||||
} from './PromptPanel'
|
||||
|
||||
type AiPromptProps = {
|
||||
/** Looks up prompt text from `aiPrompts`. */
|
||||
@@ -30,9 +36,11 @@ function AiPrompt({ id }: AiPromptProps) {
|
||||
return (
|
||||
<PromptPanel>
|
||||
<Prompt value="prompt" expandable>
|
||||
<PromptTitle icon={<Sparkles />}>AI Prompt</PromptTitle>
|
||||
<PromptTitle>Agent Prompt</PromptTitle>
|
||||
<PromptCopy>{prompt}</PromptCopy>
|
||||
<PromptContent>{prompt}</PromptContent>
|
||||
<PromptContent>
|
||||
<PromptMarkdown>{prompt}</PromptMarkdown>
|
||||
</PromptContent>
|
||||
</Prompt>
|
||||
</PromptPanel>
|
||||
)
|
||||
|
||||
@@ -180,7 +180,17 @@ function CrossfadeIcon({
|
||||
)
|
||||
}
|
||||
|
||||
export function CodeCopyButton({ className, content }: { className?: string; content: string }) {
|
||||
export function CodeCopyButton({
|
||||
className,
|
||||
content,
|
||||
label = 'Copy code',
|
||||
copiedLabel = 'Code copied',
|
||||
}: {
|
||||
className?: string
|
||||
content: string
|
||||
label?: string
|
||||
copiedLabel?: string
|
||||
}) {
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -203,7 +213,7 @@ export function CodeCopyButton({ className, content }: { className?: string; con
|
||||
return (
|
||||
<>
|
||||
<span className="sr-only" aria-live="polite">
|
||||
{copied ? 'Code copied' : ''}
|
||||
{copied ? copiedLabel : ''}
|
||||
</span>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -218,14 +228,14 @@ export function CodeCopyButton({ className, content }: { className?: string; con
|
||||
'hover:bg-[var(--btn-active)]',
|
||||
className
|
||||
)}
|
||||
aria-label="Copy code"
|
||||
aria-label={label}
|
||||
// Tooltip repeats the label; the description would read the name twice
|
||||
aria-describedby={undefined}
|
||||
>
|
||||
<CrossfadeIcon active={copied} activeIcon={Check} inactiveIcon={Copy} />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Copy code</TooltipContent>
|
||||
<TooltipContent>{label}</TooltipContent>
|
||||
</Tooltip>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
'use client'
|
||||
|
||||
import { Check, Copy } from 'lucide-react'
|
||||
import {
|
||||
Children,
|
||||
isValidElement,
|
||||
useEffect,
|
||||
useId,
|
||||
useState,
|
||||
type ReactElement,
|
||||
type ReactNode,
|
||||
} from 'react'
|
||||
import { ChevronDown } from 'lucide-react'
|
||||
import { Children, isValidElement, useId, useState, type ReactElement, type ReactNode } from 'react'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
// shadcn tabs from packages/ui/src/components/shadcn/ui/tabs.tsx
|
||||
import { cn, copyToClipboard, Tabs, TabsContent, TabsIndicator, TabsList, TabsTrigger } from 'ui'
|
||||
import { cn, Tabs, TabsContent, TabsIndicator, TabsList, TabsTrigger } from 'ui'
|
||||
|
||||
import { CodeCopyButton } from './CodeBlock/CodeBlock.client'
|
||||
|
||||
type PromptTitleProps = {
|
||||
children: ReactNode
|
||||
icon?: ReactNode
|
||||
}
|
||||
|
||||
type PromptCopyProps = {
|
||||
@@ -44,7 +38,6 @@ type PromptPanelProps = {
|
||||
type CollectedPrompt = {
|
||||
value: string
|
||||
title: ReactNode
|
||||
icon?: ReactNode
|
||||
copyValue: string
|
||||
content: ReactNode
|
||||
expandable: boolean
|
||||
@@ -82,16 +75,13 @@ function isElementOfType(child: ReactNode, type: { displayName?: string }) {
|
||||
|
||||
function collectPrompt(prompt: ReactElement<PromptProps>, index: number): CollectedPrompt {
|
||||
let title: ReactNode = `Prompt ${index + 1}`
|
||||
let icon: ReactNode | undefined
|
||||
let copyValue = ''
|
||||
let content: ReactNode = null
|
||||
let shimmer = true
|
||||
|
||||
Children.forEach(prompt.props.children, (child) => {
|
||||
if (isElementOfType(child, PromptTitle)) {
|
||||
const props = (child as ReactElement<PromptTitleProps>).props
|
||||
title = props.children
|
||||
icon = props.icon
|
||||
title = (child as ReactElement<PromptTitleProps>).props.children
|
||||
} else if (isElementOfType(child, PromptCopy)) {
|
||||
copyValue = (child as ReactElement<PromptCopyProps>).props.children
|
||||
} else if (isElementOfType(child, PromptContent)) {
|
||||
@@ -104,7 +94,6 @@ function collectPrompt(prompt: ReactElement<PromptProps>, index: number): Collec
|
||||
return {
|
||||
value: prompt.props.value ?? `prompt-${index}`,
|
||||
title,
|
||||
icon,
|
||||
copyValue,
|
||||
content,
|
||||
expandable: Boolean(prompt.props.expandable),
|
||||
@@ -120,24 +109,59 @@ function collectPrompts(children: ReactNode): CollectedPrompt[] {
|
||||
|
||||
function ExpandableContent({ children }: { children: ReactNode }) {
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
const [isHoverRevealPaused, setIsHoverRevealPaused] = useState(false)
|
||||
|
||||
const handleReelPointerLeave = () => setIsHoverRevealPaused(false)
|
||||
const handleToggle = () => {
|
||||
if (isExpanded) setIsHoverRevealPaused(true)
|
||||
setIsExpanded((expanded) => !expanded)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="relative">
|
||||
<div className={cn(!isExpanded && 'max-h-28 overflow-hidden')}>{children}</div>
|
||||
{!isExpanded && (
|
||||
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-12 bg-gradient-to-t from-background to-transparent" />
|
||||
<div
|
||||
className="group/expand relative h-full px-4 py-3.5"
|
||||
onPointerLeave={handleReelPointerLeave}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'overflow-hidden [interpolate-size:allow-keywords] transition-[max-height] duration-300 ease-out motion-reduce:transition-none',
|
||||
isExpanded ? 'max-h-max' : 'max-h-28'
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
onClick={() => setIsExpanded((expanded) => !expanded)}
|
||||
className="mt-2 cursor-pointer text-sm text-brand-link transition-colors hover:text-foreground focus-ring"
|
||||
aria-expanded={isExpanded}
|
||||
>
|
||||
{isExpanded ? 'Show less' : 'Show more'}
|
||||
</button>
|
||||
{children}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
'pointer-events-none absolute inset-x-4 bottom-3.5 h-16 bg-gradient-to-t from-background-200 to-transparent',
|
||||
'transition-opacity duration-300 ease-out motion-reduce:transition-none',
|
||||
isExpanded && 'opacity-0'
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
className={cn('flex justify-center', isExpanded ? 'mt-3' : 'absolute inset-x-0 bottom-3.5')}
|
||||
>
|
||||
<button
|
||||
key={String(isExpanded)}
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
onClick={handleToggle}
|
||||
className={cn(
|
||||
'inline-flex cursor-pointer items-center gap-1.5 rounded-full pl-2.5 pr-3 py-1 text-xs text-foreground',
|
||||
'bg-surface-200/90 shadow-sm ring-1 ring-foreground/10 backdrop-blur-sm',
|
||||
'transition-[opacity,translate,background-color,scale] duration-200 ease-out',
|
||||
'hover:bg-surface-300 active:scale-[0.96] focus-ring',
|
||||
!isExpanded &&
|
||||
'translate-y-1 opacity-0 focus-visible:translate-y-0 focus-visible:opacity-100 pointer-coarse:translate-y-0 pointer-coarse:opacity-100 motion-reduce:translate-y-0',
|
||||
!isExpanded &&
|
||||
!isHoverRevealPaused &&
|
||||
'group-hover/expand:translate-y-0 group-hover/expand:opacity-100'
|
||||
)}
|
||||
aria-expanded={isExpanded}
|
||||
>
|
||||
<ChevronDown aria-hidden="true" size={14} className={cn(isExpanded && 'rotate-180')} />
|
||||
{isExpanded ? 'Show less' : 'Show more'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -167,54 +191,42 @@ function PromptBody({
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="px-4 py-3.5 text-sm leading-6 text-foreground-light font-normal">
|
||||
<div
|
||||
className={cn(
|
||||
'h-full bg-200 text-sm leading-6 text-foreground-light font-normal',
|
||||
!prompt.expandable && 'px-4 py-3.5'
|
||||
)}
|
||||
>
|
||||
{prompt.expandable ? <ExpandableContent>{content}</ExpandableContent> : content}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function CopyButton({ label, value }: { label: string; value: string }) {
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!copied) return
|
||||
|
||||
const timeout = window.setTimeout(() => setCopied(false), 2000)
|
||||
return () => window.clearTimeout(timeout)
|
||||
}, [copied])
|
||||
|
||||
/** Renders a Markdown prompt string as the panel body, with backticks as code chips. */
|
||||
function PromptMarkdown({ children }: { children: string }) {
|
||||
return (
|
||||
<button
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
copyToClipboard(value, () => {
|
||||
setCopied(true)
|
||||
})
|
||||
}}
|
||||
className="cursor-pointer rounded-sm p-1.5 text-foreground-muted transition-colors hover:bg-surface-200 hover:text-foreground focus-ring"
|
||||
aria-label={copied ? `${label} copied` : `Copy ${label}`}
|
||||
title={copied ? 'Copied' : 'Copy to clipboard'}
|
||||
>
|
||||
{copied ? <Check size={14} /> : <Copy size={14} />}
|
||||
</button>
|
||||
<div className="not-prose flex flex-col gap-2 [&_ol]:list-decimal [&_ol]:pl-5 [&_ul]:list-disc [&_ul]:pl-5 [&_li+li]:mt-1">
|
||||
<ReactMarkdown components={MARKDOWN_COMPONENTS}>{children}</ReactMarkdown>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function TabLabel({ icon, children }: { icon?: ReactNode; children: ReactNode }) {
|
||||
/** Inline code chip for prompt bodies; opts out of the text shimmer. */
|
||||
function PromptCode({ children }: { children?: ReactNode }) {
|
||||
return (
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
{icon && (
|
||||
<span aria-hidden="true" className="text-tertiary-foreground [&_svg]:size-3.5">
|
||||
{icon}
|
||||
</span>
|
||||
)}
|
||||
<code className="shimmer-none rounded bg-surface-200 px-1 py-0.5 font-mono text-xs text-foreground">
|
||||
{children}
|
||||
</span>
|
||||
</code>
|
||||
)
|
||||
}
|
||||
|
||||
const tabTriggerClassName = 'h-full px-0 py-0 text-xs shadow-none data-[state=active]:shadow-none'
|
||||
const MARKDOWN_COMPONENTS = { code: PromptCode }
|
||||
|
||||
const tabTriggerClassName = cn(
|
||||
'h-full px-0 py-0 text-xs shadow-none data-[state=active]:shadow-none',
|
||||
'group-has-[[data-tab-indicator]]/list:border-b',
|
||||
'group-data-[tab-indicator-ready]/list:data-[state=active]:border-b-transparent'
|
||||
)
|
||||
|
||||
/**
|
||||
* Copyable prompt card. Compose one or more `<Prompt>` children; multiple
|
||||
@@ -225,7 +237,7 @@ const tabTriggerClassName = 'h-full px-0 py-0 text-xs shadow-none data-[state=ac
|
||||
* ```tsx
|
||||
* <PromptPanel>
|
||||
* <Prompt value="prompt" expandable>
|
||||
* <PromptTitle icon={<Sparkles />}>AI Prompt</PromptTitle>
|
||||
* <PromptTitle>Agent Prompt</PromptTitle>
|
||||
* <PromptCopy>Plain text copied to the clipboard</PromptCopy>
|
||||
* <PromptContent>Rich content shown in the panel</PromptContent>
|
||||
* </Prompt>
|
||||
@@ -245,13 +257,14 @@ function PromptPanel({ children, className }: PromptPanelProps) {
|
||||
const hasTabs = prompts.length > 1
|
||||
const dismissShimmer = () => setShimmerEnabled(false)
|
||||
|
||||
const copyTarget = typeof activePrompt.title === 'string' ? activePrompt.title : 'content'
|
||||
const header = (
|
||||
<div className="flex h-11 items-center justify-between border-b bg-surface-75 px-4">
|
||||
<div className="flex h-11 items-center justify-between pl-4 pr-2 shadow-[inset_0_-1px_0_0_var(--border-default)] [--btn-active:color-mix(in_srgb,var(--foreground)_4%,var(--background-200))]">
|
||||
{hasTabs ? (
|
||||
<TabsList className="h-full gap-5 border-0 [--tab-track:transparent]">
|
||||
{prompts.map((prompt) => (
|
||||
<TabsTrigger key={prompt.value} value={prompt.value} className={tabTriggerClassName}>
|
||||
<TabLabel icon={prompt.icon}>{prompt.title}</TabLabel>
|
||||
{prompt.title}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
<TabsIndicator />
|
||||
@@ -264,13 +277,14 @@ function PromptPanel({ children, className }: PromptPanelProps) {
|
||||
tabTriggerClassName
|
||||
)}
|
||||
>
|
||||
<TabLabel icon={prompts[0].icon}>{prompts[0].title}</TabLabel>
|
||||
{prompts[0].title}
|
||||
</span>
|
||||
)}
|
||||
<CopyButton
|
||||
<CodeCopyButton
|
||||
key={activePrompt.value}
|
||||
label={typeof activePrompt.title === 'string' ? activePrompt.title : 'content'}
|
||||
value={activePrompt.copyValue}
|
||||
content={activePrompt.copyValue}
|
||||
label={`Copy ${copyTarget}`}
|
||||
copiedLabel={`${copyTarget} copied`}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
@@ -283,7 +297,7 @@ function PromptPanel({ children, className }: PromptPanelProps) {
|
||||
onFocusCapture={dismissShimmer}
|
||||
onPointerEnter={dismissShimmer}
|
||||
className={cn(
|
||||
'w-full overflow-hidden rounded-lg border bg-background shadow-sm',
|
||||
'w-full overflow-hidden rounded-lg border border-default shadow-codeblock',
|
||||
className
|
||||
)}
|
||||
>
|
||||
@@ -301,7 +315,10 @@ function PromptPanel({ children, className }: PromptPanelProps) {
|
||||
activationMode="manual"
|
||||
onFocusCapture={dismissShimmer}
|
||||
onPointerEnter={dismissShimmer}
|
||||
className={cn('w-full overflow-hidden rounded-lg border bg-background shadow-sm', className)}
|
||||
className={cn(
|
||||
'w-full overflow-hidden rounded-lg border border-default shadow-codeblock',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{header}
|
||||
{/* Stack panes in one grid cell so the panel keeps the tallest tab's height. */}
|
||||
@@ -314,6 +331,8 @@ function PromptPanel({ children, className }: PromptPanelProps) {
|
||||
key={prompt.value}
|
||||
value={prompt.value}
|
||||
forceMount
|
||||
// pane's own controls remain reachable
|
||||
tabIndex={-1}
|
||||
inert={!isActive}
|
||||
aria-hidden={!isActive}
|
||||
className="col-start-1 row-start-1 m-0 data-[state=inactive]:invisible data-[state=inactive]:pointer-events-none"
|
||||
@@ -327,5 +346,5 @@ function PromptPanel({ children, className }: PromptPanelProps) {
|
||||
)
|
||||
}
|
||||
|
||||
export { Prompt, PromptContent, PromptCopy, PromptPanel, PromptTitle }
|
||||
export { Prompt, PromptCode, PromptContent, PromptCopy, PromptMarkdown, PromptPanel, PromptTitle }
|
||||
export type { PromptContentProps, PromptCopyProps, PromptPanelProps, PromptProps, PromptTitleProps }
|
||||
@@ -14,7 +14,7 @@ describe('AiPrompt markdown schema', () => {
|
||||
props: { id: 'nextjs', includeInMarkdown: true },
|
||||
})
|
||||
|
||||
expect(markdown).toContain('**AI Prompt**')
|
||||
expect(markdown).toContain('**Agent Prompt**')
|
||||
expect(markdown).toContain('Help me add Supabase to my Next.js project.')
|
||||
expect(markdown).toContain('```text')
|
||||
})
|
||||
|
||||
@@ -17,5 +17,5 @@ export function AiPrompt({ props }: HandlerContext): string {
|
||||
throw new Error(`Unknown AiPrompt id: ${id}`)
|
||||
}
|
||||
|
||||
return `**AI Prompt**\n\n${toMarkdown({ type: 'code', lang: 'text', value: prompt }).trimEnd()}`
|
||||
return `**Agent Prompt**\n\n${toMarkdown({ type: 'code', lang: 'text', value: prompt }).trimEnd()}`
|
||||
}
|
||||
@@ -18,7 +18,8 @@ const HomeLayout = ({ children }: PropsWithChildren) => {
|
||||
lang="bash"
|
||||
lineNumbers={false}
|
||||
hideControls
|
||||
className="!-mx-4 !-my-3.5 !border-0 !rounded-none !bg-transparent [&_.code-content]:!px-4 [&_.code-content]:!py-3.5"
|
||||
compact
|
||||
className="-mx-4 w-auto bg-transparent [&_.code-content]:px-4 [&_.code-content]:py-0"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
Reference in new issue
Block a user