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 }) => {
|
export const ExportChatButton = ({ exportChat }: { exportChat?: () => void }) => {
|
||||||
return (
|
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.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">
|
<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
|
Export
|
||||||
|
|||||||
@@ -1,88 +1,21 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { workbenchStore } from '~/lib/stores/workbench';
|
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 { 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 {
|
interface HeaderActionButtonsProps {
|
||||||
chatStarted: boolean;
|
chatStarted: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HeaderActionButtons({ chatStarted }: HeaderActionButtonsProps) {
|
export function HeaderActionButtons({ chatStarted: _chatStarted }: HeaderActionButtonsProps) {
|
||||||
const [activePreviewIndex] = useState(0);
|
const [activePreviewIndex] = useState(0);
|
||||||
const previews = useStore(workbenchStore.previews);
|
const previews = useStore(workbenchStore.previews);
|
||||||
const activePreview = previews[activePreviewIndex];
|
const activePreview = previews[activePreviewIndex];
|
||||||
const isStreaming = useStore(streamingState);
|
|
||||||
const { exportChat } = useChatHistory();
|
|
||||||
const [isSyncing, setIsSyncing] = useState(false);
|
|
||||||
|
|
||||||
const shouldShowButtons = !isStreaming && activePreview;
|
const shouldShowButtons = 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);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1">
|
<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 */}
|
{/* Deploy Button */}
|
||||||
{shouldShowButtons && <DeployButton />}
|
{shouldShowButtons && <DeployButton />}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import {
|
|||||||
type OnScrollCallback as OnEditorScroll,
|
type OnScrollCallback as OnEditorScroll,
|
||||||
} from '~/components/editor/codemirror/CodeMirrorEditor';
|
} from '~/components/editor/codemirror/CodeMirrorEditor';
|
||||||
import { IconButton } from '~/components/ui/IconButton';
|
import { IconButton } from '~/components/ui/IconButton';
|
||||||
import { PanelHeaderButton } from '~/components/ui/PanelHeaderButton';
|
|
||||||
import { Slider, type SliderOptions } from '~/components/ui/Slider';
|
import { Slider, type SliderOptions } from '~/components/ui/Slider';
|
||||||
import { workbenchStore, type WorkbenchViewType } from '~/lib/stores/workbench';
|
import { workbenchStore, type WorkbenchViewType } from '~/lib/stores/workbench';
|
||||||
import { classNames } from '~/utils/classNames';
|
import { classNames } from '~/utils/classNames';
|
||||||
@@ -26,6 +25,10 @@ import useViewport from '~/lib/hooks';
|
|||||||
import { usePreviewStore } from '~/lib/stores/previews';
|
import { usePreviewStore } from '~/lib/stores/previews';
|
||||||
import { chatStore } from '~/lib/stores/chat';
|
import { chatStore } from '~/lib/stores/chat';
|
||||||
import type { ElementInfo } from './Inspector';
|
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 {
|
interface WorkspaceProps {
|
||||||
chatStarted?: boolean;
|
chatStarted?: boolean;
|
||||||
@@ -302,6 +305,9 @@ export const Workbench = memo(
|
|||||||
const canHideChat = showWorkbench || !showChat;
|
const canHideChat = showWorkbench || !showChat;
|
||||||
|
|
||||||
const isSmallViewport = useViewport(1024);
|
const isSmallViewport = useViewport(1024);
|
||||||
|
const streaming = useStore(streamingState);
|
||||||
|
const { exportChat } = useChatHistory();
|
||||||
|
const [isSyncing, setIsSyncing] = useState(false);
|
||||||
|
|
||||||
const setSelectedView = (view: WorkbenchViewType) => {
|
const setSelectedView = (view: WorkbenchViewType) => {
|
||||||
workbenchStore.currentView.set(view);
|
workbenchStore.currentView.set(view);
|
||||||
@@ -351,6 +357,21 @@ export const Workbench = memo(
|
|||||||
workbenchStore.currentView.set('diff');
|
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 (
|
return (
|
||||||
chatStarted && (
|
chatStarted && (
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -386,15 +407,63 @@ export const Workbench = memo(
|
|||||||
<div className="ml-auto" />
|
<div className="ml-auto" />
|
||||||
{selectedView === 'code' && (
|
{selectedView === 'code' && (
|
||||||
<div className="flex overflow-y-auto">
|
<div className="flex overflow-y-auto">
|
||||||
<PanelHeaderButton
|
{/* Export Chat Button */}
|
||||||
className="mr-1 text-sm"
|
<ExportChatButton exportChat={exportChat} />
|
||||||
onClick={() => {
|
|
||||||
workbenchStore.toggleTerminal(!workbenchStore.showTerminal.get());
|
{/* Sync Button */}
|
||||||
}}
|
<div className="flex border border-bolt-elements-borderColor rounded-md overflow-hidden ml-1">
|
||||||
>
|
<DropdownMenu.Root>
|
||||||
<div className="i-ph:terminal" />
|
<DropdownMenu.Trigger
|
||||||
Toggle Terminal
|
disabled={isSyncing || streaming}
|
||||||
</PanelHeaderButton>
|
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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user