feat: move export/sync buttons to workbench and standardize styling
- Move Export and Sync buttons from header to workbench editor panel - Position buttons in front of Toggle Terminal button - Standardize button styling with consistent: * Accent colored background and white text * Same padding (px-3 py-1.5) and font size (text-xs) * Consistent border styling and hover states - Fix font size conflicts by removing parent text-sm classes - Clean up unused imports and fix prettier formatting - Ensure dropdown items have consistent text-sm font size Resolves button styling inconsistencies and improves UX
This commit is contained in:
@@ -4,7 +4,7 @@ import { classNames } from '~/utils/classNames';
|
||||
|
||||
export const ExportChatButton = ({ exportChat }: { exportChat?: () => void }) => {
|
||||
return (
|
||||
<div className="flex border border-bolt-elements-borderColor rounded-md overflow-hidden mr-2 text-sm">
|
||||
<div className="flex border border-bolt-elements-borderColor rounded-md overflow-hidden">
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger className="rounded-md items-center justify-center [&:is(:disabled,.disabled)]:cursor-not-allowed [&:is(:disabled,.disabled)]:opacity-60 px-3 py-1.5 text-xs bg-accent-500 text-white hover:text-bolt-elements-item-contentAccent [&:not(:disabled,.disabled)]:hover:bg-bolt-elements-button-primary-backgroundHover outline-accent-500 flex gap-1.7">
|
||||
Export
|
||||
|
||||
@@ -1,88 +1,21 @@
|
||||
import { useState } from 'react';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { workbenchStore } from '~/lib/stores/workbench';
|
||||
import { useState, useCallback } from 'react';
|
||||
import { streamingState } from '~/lib/stores/streaming';
|
||||
import { ExportChatButton } from '~/components/chat/chatExportAndImport/ExportChatButton';
|
||||
import { useChatHistory } from '~/lib/persistence';
|
||||
import { DeployButton } from '~/components/deploy/DeployButton';
|
||||
import { toast } from 'react-toastify';
|
||||
import { classNames } from '~/utils/classNames';
|
||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
||||
|
||||
interface HeaderActionButtonsProps {
|
||||
chatStarted: boolean;
|
||||
}
|
||||
|
||||
export function HeaderActionButtons({ chatStarted }: HeaderActionButtonsProps) {
|
||||
export function HeaderActionButtons({ chatStarted: _chatStarted }: HeaderActionButtonsProps) {
|
||||
const [activePreviewIndex] = useState(0);
|
||||
const previews = useStore(workbenchStore.previews);
|
||||
const activePreview = previews[activePreviewIndex];
|
||||
const isStreaming = useStore(streamingState);
|
||||
const { exportChat } = useChatHistory();
|
||||
const [isSyncing, setIsSyncing] = useState(false);
|
||||
|
||||
const shouldShowButtons = !isStreaming && activePreview;
|
||||
|
||||
const handleSyncFiles = useCallback(async () => {
|
||||
setIsSyncing(true);
|
||||
|
||||
try {
|
||||
const directoryHandle = await window.showDirectoryPicker();
|
||||
await workbenchStore.syncFiles(directoryHandle);
|
||||
toast.success('Files synced successfully');
|
||||
} catch (error) {
|
||||
console.error('Error syncing files:', error);
|
||||
toast.error('Failed to sync files');
|
||||
} finally {
|
||||
setIsSyncing(false);
|
||||
}
|
||||
}, []);
|
||||
const shouldShowButtons = activePreview;
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1">
|
||||
{/* Export Chat Button */}
|
||||
{chatStarted && shouldShowButtons && <ExportChatButton exportChat={exportChat} />}
|
||||
|
||||
{/* Sync Button */}
|
||||
{shouldShowButtons && (
|
||||
<div className="flex border border-bolt-elements-borderColor rounded-md overflow-hidden text-sm">
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger
|
||||
disabled={isSyncing}
|
||||
className="rounded-md items-center justify-center [&:is(:disabled,.disabled)]:cursor-not-allowed [&:is(:disabled,.disabled)]:opacity-60 px-3 py-1.5 text-xs bg-accent-500 text-white hover:text-bolt-elements-item-contentAccent [&:not(:disabled,.disabled)]:hover:bg-bolt-elements-button-primary-backgroundHover outline-accent-500 flex gap-1.7"
|
||||
>
|
||||
{isSyncing ? 'Syncing...' : 'Sync'}
|
||||
<span className={classNames('i-ph:caret-down transition-transform')} />
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content
|
||||
className={classNames(
|
||||
'min-w-[240px] z-[250]',
|
||||
'bg-white dark:bg-[#141414]',
|
||||
'rounded-lg shadow-lg',
|
||||
'border border-gray-200/50 dark:border-gray-800/50',
|
||||
'animate-in fade-in-0 zoom-in-95',
|
||||
'py-1',
|
||||
)}
|
||||
sideOffset={5}
|
||||
align="end"
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
className={classNames(
|
||||
'cursor-pointer flex items-center w-full px-4 py-2 text-sm text-bolt-elements-textPrimary hover:bg-bolt-elements-item-backgroundActive gap-2 rounded-md group relative',
|
||||
)}
|
||||
onClick={handleSyncFiles}
|
||||
disabled={isSyncing}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{isSyncing ? <div className="i-ph:spinner" /> : <div className="i-ph:cloud-arrow-down" />}
|
||||
<span>{isSyncing ? 'Syncing...' : 'Sync Files'}</span>
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Deploy Button */}
|
||||
{shouldShowButtons && <DeployButton />}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
type OnScrollCallback as OnEditorScroll,
|
||||
} from '~/components/editor/codemirror/CodeMirrorEditor';
|
||||
import { IconButton } from '~/components/ui/IconButton';
|
||||
import { PanelHeaderButton } from '~/components/ui/PanelHeaderButton';
|
||||
import { Slider, type SliderOptions } from '~/components/ui/Slider';
|
||||
import { workbenchStore, type WorkbenchViewType } from '~/lib/stores/workbench';
|
||||
import { classNames } from '~/utils/classNames';
|
||||
@@ -26,6 +25,10 @@ import useViewport from '~/lib/hooks';
|
||||
import { usePreviewStore } from '~/lib/stores/previews';
|
||||
import { chatStore } from '~/lib/stores/chat';
|
||||
import type { ElementInfo } from './Inspector';
|
||||
import { ExportChatButton } from '~/components/chat/chatExportAndImport/ExportChatButton';
|
||||
import { useChatHistory } from '~/lib/persistence';
|
||||
import { streamingState } from '~/lib/stores/streaming';
|
||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
||||
|
||||
interface WorkspaceProps {
|
||||
chatStarted?: boolean;
|
||||
@@ -302,6 +305,9 @@ export const Workbench = memo(
|
||||
const canHideChat = showWorkbench || !showChat;
|
||||
|
||||
const isSmallViewport = useViewport(1024);
|
||||
const streaming = useStore(streamingState);
|
||||
const { exportChat } = useChatHistory();
|
||||
const [isSyncing, setIsSyncing] = useState(false);
|
||||
|
||||
const setSelectedView = (view: WorkbenchViewType) => {
|
||||
workbenchStore.currentView.set(view);
|
||||
@@ -351,6 +357,21 @@ export const Workbench = memo(
|
||||
workbenchStore.currentView.set('diff');
|
||||
}, []);
|
||||
|
||||
const handleSyncFiles = useCallback(async () => {
|
||||
setIsSyncing(true);
|
||||
|
||||
try {
|
||||
const directoryHandle = await window.showDirectoryPicker();
|
||||
await workbenchStore.syncFiles(directoryHandle);
|
||||
toast.success('Files synced successfully');
|
||||
} catch (error) {
|
||||
console.error('Error syncing files:', error);
|
||||
toast.error('Failed to sync files');
|
||||
} finally {
|
||||
setIsSyncing(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
chatStarted && (
|
||||
<motion.div
|
||||
@@ -386,15 +407,63 @@ export const Workbench = memo(
|
||||
<div className="ml-auto" />
|
||||
{selectedView === 'code' && (
|
||||
<div className="flex overflow-y-auto">
|
||||
<PanelHeaderButton
|
||||
className="mr-1 text-sm"
|
||||
onClick={() => {
|
||||
workbenchStore.toggleTerminal(!workbenchStore.showTerminal.get());
|
||||
}}
|
||||
>
|
||||
<div className="i-ph:terminal" />
|
||||
Toggle Terminal
|
||||
</PanelHeaderButton>
|
||||
{/* Export Chat Button */}
|
||||
<ExportChatButton exportChat={exportChat} />
|
||||
|
||||
{/* Sync Button */}
|
||||
<div className="flex border border-bolt-elements-borderColor rounded-md overflow-hidden ml-1">
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger
|
||||
disabled={isSyncing || streaming}
|
||||
className="rounded-md items-center justify-center [&:is(:disabled,.disabled)]:cursor-not-allowed [&:is(:disabled,.disabled)]:opacity-60 px-3 py-1.5 text-xs bg-accent-500 text-white hover:text-bolt-elements-item-contentAccent [&:not(:disabled,.disabled)]:hover:bg-bolt-elements-button-primary-backgroundHover outline-accent-500 flex gap-1.7"
|
||||
>
|
||||
{isSyncing ? 'Syncing...' : 'Sync'}
|
||||
<span className={classNames('i-ph:caret-down transition-transform')} />
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content
|
||||
className={classNames(
|
||||
'min-w-[240px] z-[250]',
|
||||
'bg-white dark:bg-[#141414]',
|
||||
'rounded-lg shadow-lg',
|
||||
'border border-gray-200/50 dark:border-gray-800/50',
|
||||
'animate-in fade-in-0 zoom-in-95',
|
||||
'py-1',
|
||||
)}
|
||||
sideOffset={5}
|
||||
align="end"
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
className={classNames(
|
||||
'cursor-pointer flex items-center w-full px-4 py-2 text-sm text-bolt-elements-textPrimary hover:bg-bolt-elements-item-backgroundActive gap-2 rounded-md group relative',
|
||||
)}
|
||||
onClick={handleSyncFiles}
|
||||
disabled={isSyncing}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{isSyncing ? (
|
||||
<div className="i-ph:spinner" />
|
||||
) : (
|
||||
<div className="i-ph:cloud-arrow-down" />
|
||||
)}
|
||||
<span>{isSyncing ? 'Syncing...' : 'Sync Files'}</span>
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
|
||||
{/* Toggle Terminal Button */}
|
||||
<div className="flex border border-bolt-elements-borderColor rounded-md overflow-hidden ml-1">
|
||||
<button
|
||||
onClick={() => {
|
||||
workbenchStore.toggleTerminal(!workbenchStore.showTerminal.get());
|
||||
}}
|
||||
className="rounded-md items-center justify-center [&:is(:disabled,.disabled)]:cursor-not-allowed [&:is(:disabled,.disabled)]:opacity-60 px-3 py-1.5 text-xs bg-accent-500 text-white hover:text-bolt-elements-item-contentAccent [&:not(:disabled,.disabled)]:hover:bg-bolt-elements-button-primary-backgroundHover outline-accent-500 flex gap-1.7"
|
||||
>
|
||||
<div className="i-ph:terminal" />
|
||||
Toggle Terminal
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user