From a5ed695cb344b1b327a041417e5f6fe988d0c649 Mon Sep 17 00:00:00 2001 From: Dominic Elm Date: Thu, 25 Jul 2024 16:34:27 +0200 Subject: [PATCH] refactor(workbench): add slider to switch between code or preview (#12) --- packages/bolt/app/components/ui/Slider.tsx | 62 +++++++++++++ .../bolt/app/components/workbench/Preview.tsx | 7 -- .../components/workbench/Workbench.client.tsx | 92 +++++++++++++------ packages/bolt/app/utils/react.ts | 6 ++ 4 files changed, 134 insertions(+), 33 deletions(-) create mode 100644 packages/bolt/app/components/ui/Slider.tsx create mode 100644 packages/bolt/app/utils/react.ts diff --git a/packages/bolt/app/components/ui/Slider.tsx b/packages/bolt/app/components/ui/Slider.tsx new file mode 100644 index 0000000..06e7610 --- /dev/null +++ b/packages/bolt/app/components/ui/Slider.tsx @@ -0,0 +1,62 @@ +import { motion } from 'framer-motion'; +import { memo } from 'react'; +import { classNames } from '~/utils/classNames'; +import { genericMemo } from '~/utils/react'; + +interface SliderOption { + value: T; + text: string; +} + +export interface SliderOptions { + left: SliderOption; + right: SliderOption; +} + +interface SliderProps { + selected: T; + options: SliderOptions; + setSelected?: (selected: T) => void; +} + +export const Slider = genericMemo(({ selected, options, setSelected }: SliderProps) => { + const isLeftSelected = selected === options.left.value; + + return ( +
+ setSelected?.(options.left.value)}> + {options.left.text} + + setSelected?.(options.right.value)}> + {options.right.text} + +
+ ); +}); + +interface SliderButtonProps { + selected: boolean; + children: string | JSX.Element | Array; + setSelected: () => void; +} + +const SliderButton = memo(({ selected, children, setSelected }: SliderButtonProps) => { + return ( + + ); +}); diff --git a/packages/bolt/app/components/workbench/Preview.tsx b/packages/bolt/app/components/workbench/Preview.tsx index 38c6b49..840dfe2 100644 --- a/packages/bolt/app/components/workbench/Preview.tsx +++ b/packages/bolt/app/components/workbench/Preview.tsx @@ -36,13 +36,6 @@ export const Preview = memo(() => { return (
-
-
-
- Preview -
-
-
diff --git a/packages/bolt/app/components/workbench/Workbench.client.tsx b/packages/bolt/app/components/workbench/Workbench.client.tsx index 3d9ebf4..e3bd7e6 100644 --- a/packages/bolt/app/components/workbench/Workbench.client.tsx +++ b/packages/bolt/app/components/workbench/Workbench.client.tsx @@ -1,13 +1,14 @@ import { useStore } from '@nanostores/react'; -import { AnimatePresence, motion, type Variants } from 'framer-motion'; -import { memo, useCallback, useEffect } from 'react'; -import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; +import { AnimatePresence, motion, type HTMLMotionProps, type Variants } from 'framer-motion'; +import { computed } from 'nanostores'; +import { memo, useCallback, useEffect, useState } from 'react'; import { toast } from 'react-toastify'; import { type OnChangeCallback as OnEditorChange, type OnScrollCallback as OnEditorScroll, } from '~/components/editor/codemirror/CodeMirrorEditor'; import { IconButton } from '~/components/ui/IconButton'; +import { Slider, type SliderOptions } from '~/components/ui/Slider'; import { workbenchStore } from '~/lib/stores/workbench'; import { cubicEasingFn } from '~/utils/easings'; import { renderLogger } from '~/utils/logger'; @@ -19,6 +20,21 @@ interface WorkspaceProps { isStreaming?: boolean; } +type ViewType = 'code' | 'preview'; + +const viewTransition = { ease: cubicEasingFn }; + +const sliderOptions: SliderOptions = { + left: { + value: 'code', + text: 'Code', + }, + right: { + value: 'preview', + text: 'Preview', + }, +}; + const workbenchVariants = { closed: { width: 0, @@ -39,13 +55,21 @@ const workbenchVariants = { export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) => { renderLogger.trace('Workbench'); + const hasPreview = useStore(computed(workbenchStore.previews, (previews) => previews.length > 0)); const showWorkbench = useStore(workbenchStore.showWorkbench); const selectedFile = useStore(workbenchStore.selectedFile); const currentDocument = useStore(workbenchStore.currentDocument); const unsavedFiles = useStore(workbenchStore.unsavedFiles); - const files = useStore(workbenchStore.files); + const [selectedView, setSelectedView] = useState(hasPreview ? 'preview' : 'code'); + + useEffect(() => { + if (hasPreview) { + setSelectedView('preview'); + } + }, [hasPreview]); + useEffect(() => { workbenchStore.setDocuments(files); }, [files]); @@ -79,7 +103,8 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
-
+
+ }} />
-
- - - - - - - - - +
+ + + + + +
@@ -119,3 +147,15 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) => ) ); }); + +interface ViewProps extends HTMLMotionProps<'div'> { + children: JSX.Element; +} + +const View = memo(({ children, ...props }: ViewProps) => { + return ( + + {children} + + ); +}); diff --git a/packages/bolt/app/utils/react.ts b/packages/bolt/app/utils/react.ts new file mode 100644 index 0000000..f5d2b02 --- /dev/null +++ b/packages/bolt/app/utils/react.ts @@ -0,0 +1,6 @@ +import { memo } from 'react'; + +export const genericMemo: >( + component: T, + propsAreEqual?: (prevProps: React.ComponentProps, nextProps: React.ComponentProps) => boolean, +) => T & { displayName?: string } = memo;