feat: implement light and dark theme (#30)

This commit is contained in:
Dominic Elm
2024-08-08 15:56:36 +02:00
committed by GitHub
parent e8447db417
commit 4b59a79baa
35 changed files with 799 additions and 479 deletions

View File

@@ -125,7 +125,7 @@ export const EditorPanel = memo(
<Panel defaultSize={showTerminal ? DEFAULT_EDITOR_SIZE : 100} minSize={20}>
<PanelGroup direction="horizontal">
<Panel defaultSize={25} minSize={10} collapsible>
<div className="flex flex-col border-r h-full">
<div className="flex flex-col border-r border-bolt-elements-borderColor h-full">
<PanelHeader>
<div className="i-ph:tree-structure-duotone shrink-0" />
Files
@@ -143,6 +143,7 @@ export const EditorPanel = memo(
<PanelHeader>
{activeFile && (
<div className="flex items-center flex-1 text-sm">
<div className="i-ph:file-duotone mr-2" />
{activeFile} {isStreaming && <span className="text-xs ml-1 font-semibold">(read-only)</span>}
{activeFileUnsaved && (
<div className="flex gap-1 ml-auto -mr-1.5">
@@ -191,50 +192,58 @@ export const EditorPanel = memo(
}
}}
>
<div className="border-t h-full flex flex-col">
<div className="flex items-center bg-gray-50 min-h-[34px]">
<div className="h-full">
<div className="bg-bolt-elements-terminals-background h-full flex flex-col">
<div className="flex items-center bg-bolt-elements-background-depth-2 border-y border-bolt-elements-borderColor gap-1.5 min-h-[34px] p-2">
{Array.from({ length: terminalCount }, (_, index) => {
const isActive = activeTerminal === index;
return (
<button
key={index}
className={classNames(
'flex items-center text-sm cursor-pointer gap-1.5 px-3 py-2 h-full whitespace-nowrap rounded-full',
{
'bg-bolt-elements-terminals-buttonBackground text-bolt-elements-textPrimary': isActive,
'bg-bolt-elements-background-depth-2 text-bolt-elements-textSecondary hover:bg-bolt-elements-terminals-buttonBackground':
!isActive,
},
)}
onClick={() => setActiveTerminal(index)}
>
<div className="i-ph:terminal-window-duotone text-lg" />
Terminal {terminalCount > 1 && index + 1}
</button>
);
})}
{terminalCount < MAX_TERMINALS && <IconButton icon="i-ph:plus" size="md" onClick={addTerminal} />}
<IconButton
className="ml-auto"
icon="i-ph:caret-down"
title="Close"
size="md"
onClick={() => workbenchStore.toggleTerminal(false)}
/>
</div>
{Array.from({ length: terminalCount }, (_, index) => {
const isActive = activeTerminal === index;
return (
<button
<Terminal
key={index}
className={classNames(
'flex items-center text-sm bg-transparent cursor-pointer gap-1.5 px-3.5 h-full whitespace-nowrap',
{
'bg-white': isActive,
'hover:bg-gray-100': !isActive,
},
)}
onClick={() => setActiveTerminal(index)}
>
<div className="i-ph:terminal-window-duotone text-md" />
Terminal {terminalCount > 1 && index + 1}
</button>
className={classNames('h-full overflow-hidden', {
hidden: !isActive,
})}
ref={(ref) => {
terminalRefs.current.push(ref);
}}
onTerminalReady={(terminal) => workbenchStore.attachTerminal(terminal)}
onTerminalResize={(cols, rows) => workbenchStore.onTerminalResize(cols, rows)}
theme={theme}
/>
);
})}
{terminalCount < MAX_TERMINALS && (
<IconButton className="ml-2" icon="i-ph:plus" size="md" onClick={addTerminal} />
)}
</div>
{Array.from({ length: terminalCount }, (_, index) => {
const isActive = activeTerminal === index;
return (
<Terminal
key={index}
className={classNames('h-full overflow-hidden', {
hidden: !isActive,
})}
ref={(ref) => {
terminalRefs.current.push(ref);
}}
onTerminalReady={(terminal) => workbenchStore.attachTerminal(terminal)}
onTerminalResize={(cols, rows) => workbenchStore.onTerminalResize(cols, rows)}
theme={theme}
/>
);
})}
</div>
</Panel>
</PanelGroup>

View File

@@ -3,7 +3,7 @@ import type { FileMap } from '~/lib/stores/files';
import { classNames } from '~/utils/classNames';
import { renderLogger } from '~/utils/logger';
const NODE_PADDING_LEFT = 12;
const NODE_PADDING_LEFT = 8;
const DEFAULT_HIDDEN_FILES = [/\/node_modules\//, /\/\.next/, /\/\.astro/];
interface Props {
@@ -86,7 +86,7 @@ export const FileTree = memo(
};
return (
<div className={className}>
<div className={classNames('text-sm', className)}>
{filteredFileList.map((fileOrFolder) => {
switch (fileOrFolder.kind) {
case 'file': {
@@ -135,7 +135,7 @@ interface FolderProps {
function Folder({ folder: { depth, name }, collapsed, onClick }: FolderProps) {
return (
<NodeButton
className="group bg-white hover:bg-gray-50 text-md"
className="group bg-transparent text-bolt-elements-item-contentDefault hover:text-bolt-elements-item-contentActive hover:bg-bolt-elements-item-backgroundActive"
depth={depth}
iconClasses={classNames({
'i-ph:caret-right scale-98': collapsed,
@@ -159,18 +159,22 @@ function File({ file: { depth, name }, onClick, selected, unsavedChanges = false
return (
<NodeButton
className={classNames('group', {
'bg-white hover:bg-gray-50': !selected,
'bg-gray-100': selected,
'bg-transparent hover:bg-bolt-elements-item-backgroundActive text-bolt-elements-item-contentDefault': !selected,
'bg-bolt-elements-item-backgroundAccent text-bolt-elements-item-contentAccent': selected,
})}
depth={depth}
iconClasses={classNames('i-ph:file-duotone scale-98', {
'text-gray-600': !selected,
'group-hover:text-bolt-elements-item-contentActive': !selected,
})}
onClick={onClick}
>
<div className="flex items-center">
<div
className={classNames('flex items-center', {
'group-hover:text-bolt-elements-item-contentActive': !selected,
})}
>
<div className="flex-1 truncate pr-2">{name}</div>
{unsavedChanges && <span className="i-ph:circle-fill scale-68 shrink-0 text-warning-400" />}
{unsavedChanges && <span className="i-ph:circle-fill scale-68 shrink-0 text-orange-500" />}
</div>
</NodeButton>
);
@@ -187,8 +191,11 @@ interface ButtonProps {
function NodeButton({ depth, iconClasses, onClick, className, children }: ButtonProps) {
return (
<button
className={`flex items-center gap-1.5 w-full pr-2 border-2 border-transparent text-faded ${className ?? ''}`}
style={{ paddingLeft: `${12 + depth * NODE_PADDING_LEFT}px` }}
className={classNames(
'flex items-center gap-1.5 w-full pr-2 border-2 border-transparent text-faded py-0.5',
className,
)}
style={{ paddingLeft: `${6 + depth * NODE_PADDING_LEFT}px` }}
onClick={() => onClick?.()}
>
<div className={classNames('scale-120 shrink-0', iconClasses)}></div>

View File

@@ -56,12 +56,12 @@ export const Preview = memo(() => {
return (
<div className="w-full h-full flex flex-col">
<div className="bg-white p-2 flex items-center gap-1.5">
<div className="bg-bolt-elements-background-depth-2 p-2 flex items-center gap-1.5">
<IconButton icon="i-ph:arrow-clockwise" onClick={reloadPreview} />
<div className="flex items-center gap-1 flex-grow bg-gray-100 rounded-full px-3 py-1 text-sm text-gray-600 hover:bg-gray-200 hover:focus-within:bg-white focus-within:bg-white focus-within:ring-2 focus-within:ring-accent">
<div className="bg-white rounded-full p-[2px] -ml-1">
<div className="i-ph:info-bold text-lg" />
</div>
<div
className="flex items-center gap-1 flex-grow bg-bolt-elements-preview-addressBar-background border border-bolt-elements-borderColor text-bolt-elements-preview-addressBar-text rounded-full px-3 py-1 text-sm hover:bg-bolt-elements-preview-addressBar-backgroundHover hover:focus-within:bg-bolt-elements-preview-addressBar-backgroundActive focus-within:bg-bolt-elements-preview-addressBar-backgroundActive
focus-within-border-bolt-elements-borderColorActive focus-within:text-bolt-elements-preview-addressBar-textActive"
>
<input
ref={inputRef}
className="w-full bg-transparent outline-none"

View File

@@ -8,6 +8,7 @@ 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 } from '~/lib/stores/workbench';
import { cubicEasingFn } from '~/utils/easings';
@@ -100,13 +101,22 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
chatStarted && (
<motion.div initial="closed" animate={showWorkbench ? 'open' : 'closed'} variants={workbenchVariants}>
<div className="fixed top-[calc(var(--header-height)+1.5rem)] bottom-[calc(1.5rem-1px)] w-[50vw] mr-4 z-0">
<div className="flex flex-col bg-white border border-gray-200 shadow-sm rounded-lg overflow-hidden absolute inset-0 right-8">
<div className="flex items-center px-3 py-2 border-b border-gray-200">
<div className="flex flex-col bg-bolt-elements-background-depth-2 border border-bolt-elements-borderColor shadow-sm rounded-lg overflow-hidden absolute inset-0 right-8">
<div className="flex items-center px-3 py-2 border-b border-bolt-elements-borderColor">
<Slider selected={selectedView} options={sliderOptions} setSelected={setSelectedView} />
<PanelHeaderButton
className="ml-auto mr-1 text-sm"
onClick={() => {
workbenchStore.toggleTerminal(!workbenchStore.showTerminal.get());
}}
>
<div className="i-ph:terminal" />
Toggle Terminal
</PanelHeaderButton>
<IconButton
icon="i-ph:x-circle"
className="ml-auto -mr-1"
size="xxl"
className="-mr-1"
size="xl"
onClick={() => {
workbenchStore.showWorkbench.set(false);
}}

View File

@@ -36,7 +36,7 @@ export const Terminal = memo(
convertEol: true,
disableStdin: readonly,
theme: getTerminalTheme(readonly ? { cursor: '#00000000' } : {}),
fontSize: 13,
fontSize: 12,
fontFamily: 'Menlo, courier-new, courier, monospace',
});