Files
supabase/apps/studio/components/interfaces/Explorer/ExplorerChatToolbar.tsx
Saxon Fletcher 516ef0320f chore(studio): refine Explorer toolbar, chat, and home actions (#49748)
## Summary
- Unify Explorer toolbar actions: 16px / 2px Lucide icons,
`text-tertiary-foreground` that becomes `text-foreground` on hover
(Analyze icon goes brand on hover).
- Soften chat scroll edges with top/bottom fades, and align the composer
width with the conversation content (`px-7` + `max-w-3xl`).
- Put **Run SQL** first on Explorer home, and rename the tab-bar new-tab
item from “New query” to **Run SQL** so it matches.

## Test plan
- [ ] Open Explorer and check query, notebook, and chat toolbars: icons
are 16px, muted by default, and go to foreground on hover. Analyze on a
notebook with cells: icon goes brand on hover; empty notebook still
disables Analyze.
- [ ] Open a query tab: source menu, result settings, save, and
more-options all look like the other toolbar actions (including while
the dropdown is open).
- [ ] Open Explorer chat: scroll a long thread and confirm top/bottom
fades sit on the chat surface. Composer lines up with message width (not
inset extra).
- [ ] On Explorer home, **Run SQL** is the first card; clicking it still
opens a SQL tab.
- [ ] From the tab bar **+** menu, the first item is **Run SQL** (not
“New query”); it still creates a SQL tab. **New notebook** and **New
chat** still work.

Made with [Cursor](https://cursor.com)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **UI Improvements**
* Standardized Explorer toolbar icons for consistent sizing and visual
weight.
* Updated toolbar action colors, hover states, and keyboard-focus
visibility.
  * Reordered Explorer home actions so “Run SQL” appears first.
  * Renamed “New query” to “Run SQL” in the new-tab menu.
  * Improved query source and settings toolbar controls.
* Refined AI assistant chat layout with centered content, decorative
gradients, and improved focus styling.
  * Added hover styling for the notebook Analyze action.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 18:36:01 +10:00

143 lines
5.0 KiB
TypeScript

import { Clipboard, MessageSquare, MoreVertical, Settings, Trash } from 'lucide-react'
import { useState } from 'react'
import { toast } from 'sonner'
import {
copyToClipboard,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from 'ui'
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
import {
ExplorerToolbar,
ExplorerToolbarAction,
ExplorerToolbarActions,
ExplorerToolbarIcon,
ExplorerToolbarTitle,
} from './ExplorerToolbar'
import { AIAssistantMetadataWarning } from '@/components/ui/AIAssistantPanel/AIAssistantMetadataWarning'
import type { AssistantChatHeaderProps } from '@/components/ui/AIAssistantPanel/AssistantChat'
import { ShortcutPills } from '@/components/ui/ShortcutTooltip'
import { useAiAssistantStateSnapshot } from '@/state/ai-assistant-state'
import { SHORTCUT_DEFINITIONS, SHORTCUT_IDS } from '@/state/shortcuts/registry'
import { useShortcut } from '@/state/shortcuts/useShortcut'
interface ExplorerChatToolbarProps extends AssistantChatHeaderProps {
chatId: string
shortcutsEnabled: boolean
}
export const ExplorerChatToolbar = ({
chatId,
shortcutsEnabled,
isChatLoading,
showMetadataWarning,
updatedOptInSinceMCP,
isHipaaProjectDisallowed,
aiOptInLevel,
}: ExplorerChatToolbarProps) => {
const snap = useAiAssistantStateSnapshot()
const chat = snap.chats[chatId]
const [isOptInModalOpen, setIsOptInModalOpen] = useState(false)
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false)
const handleCopyChatId = () => {
copyToClipboard(chatId, () => toast.success(`Copied chat ID for ${chat?.name}`))
}
const handleSaveName = (name: string) => {
if (name.trim()) snap.renameChat(chatId, name.trim())
}
const handleDeleteChat = () => {
snap.deleteChat(chatId)
setIsDeleteModalOpen(false)
toast.success(`Deleted "${chat?.name}"`)
}
useShortcut(SHORTCUT_IDS.AI_ASSISTANT_COPY_CHAT_ID, handleCopyChatId, {
enabled: shortcutsEnabled && !isChatLoading,
})
useShortcut(SHORTCUT_IDS.AI_ASSISTANT_OPEN_PERMISSIONS, () => setIsOptInModalOpen(true), {
enabled: shortcutsEnabled && !isChatLoading,
})
return (
<div className="z-30 sticky top-0">
<ExplorerToolbar aria-label="Chat toolbar">
<ExplorerToolbarIcon>
<MessageSquare size={16} strokeWidth={2} />
</ExplorerToolbarIcon>
<ExplorerToolbarTitle onSaveTitle={handleSaveName}>{chat?.name ?? ''}</ExplorerToolbarTitle>
<ExplorerToolbarActions>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<ExplorerToolbarAction
aria-label="More options"
icon={<MoreVertical size={16} strokeWidth={2} />}
disabled={isChatLoading}
/>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-60">
<DropdownMenuItem className="justify-between" onClick={handleCopyChatId}>
<div className="flex items-center gap-x-2">
<Clipboard size={14} />
<span>Copy chat ID</span>
</div>
<ShortcutPills
sequence={SHORTCUT_DEFINITIONS[SHORTCUT_IDS.AI_ASSISTANT_COPY_CHAT_ID].sequence}
/>
</DropdownMenuItem>
<DropdownMenuItem
className="justify-between"
onClick={() => setIsOptInModalOpen(true)}
>
<div className="flex items-center gap-x-2">
<Settings size={14} />
<span>Permission settings</span>
</div>
<ShortcutPills
sequence={
SHORTCUT_DEFINITIONS[SHORTCUT_IDS.AI_ASSISTANT_OPEN_PERMISSIONS].sequence
}
/>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem className="gap-x-2" onClick={() => setIsDeleteModalOpen(true)}>
<Trash size={14} />
<span>Delete chat</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</ExplorerToolbarActions>
</ExplorerToolbar>
<AIAssistantMetadataWarning
visible={isOptInModalOpen}
onVisibleChange={setIsOptInModalOpen}
showMetadataWarning={showMetadataWarning}
updatedOptInSinceMCP={updatedOptInSinceMCP}
isHipaaProjectDisallowed={isHipaaProjectDisallowed}
aiOptInLevel={aiOptInLevel}
/>
<ConfirmationModal
variant="destructive"
visible={isDeleteModalOpen}
title={`Delete "${chat?.name}"?`}
confirmLabel="Delete chat"
onCancel={() => setIsDeleteModalOpen(false)}
onConfirm={handleDeleteChat}
>
<p className="text-sm text-foreground-light">
This will permanently delete this chat and its message history. This action cannot be
undone.
</p>
</ConfirmationModal>
</div>
)
}