From 1eb74b7781ffe72786371d4384cb45661d48bbb7 Mon Sep 17 00:00:00 2001 From: Thibaut Patel Date: Tue, 13 Jun 2023 16:49:44 +0200 Subject: [PATCH] feat: enable resizing the side panel (#54) - The handle has a 5px+ border around it to help users select it - Removed the code related to transitions as it wasn't working (transitions were activated on transform, transform wasn't used) --- CHANGELOG.md | 1 + src/chainlit/frontend/package-lock.json | 63 ++++++++ src/chainlit/frontend/package.json | 2 + .../src/components/element/sideView.tsx | 152 +++++++++++------- 4 files changed, 158 insertions(+), 60 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f09c48f1c6..ab9db0eda0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - New Avatar element to display avatars in messages - AskFileMessage now supports multi file uploads (small breaking change) - New settings interface including a new "Expand all" messages setting +- The element sidebar is resizable ### Fixed diff --git a/src/chainlit/frontend/package-lock.json b/src/chainlit/frontend/package-lock.json index 43ea4554c2..19ee18ae65 100644 --- a/src/chainlit/frontend/package-lock.json +++ b/src/chainlit/frontend/package-lock.json @@ -25,6 +25,7 @@ "react-hot-toast": "^2.4.0", "react-hotkeys-hook": "^4.4.0", "react-markdown": "^8.0.6", + "react-resizable": "^3.0.5", "react-router-dom": "^6.8.1", "react-string-replace": "^1.1.0", "react-syntax-highlighter": "^15.5.0", @@ -42,6 +43,7 @@ "@types/node": "^20.0.0", "@types/react": "^18.0.27", "@types/react-dom": "^18.0.10", + "@types/react-resizable": "^3.0.4", "@types/react-syntax-highlighter": "^15.5.6", "@types/react-window-infinite-loader": "^1.0.6", "@typescript-eslint/eslint-plugin": "^5.59.2", @@ -1805,6 +1807,15 @@ "@types/react": "*" } }, + "node_modules/@types/react-resizable": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/react-resizable/-/react-resizable-3.0.4.tgz", + "integrity": "sha512-+QguN9CDfC1lthq+4noG1fkxh8cqkV2Fv/Mu3mdknCCBiwwNLecnBdk1MmNNN7uJpT23Nx/aVkYsbt5NuWouFw==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-syntax-highlighter": { "version": "15.5.6", "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.6.tgz", @@ -5061,6 +5072,19 @@ "react": "^18.2.0" } }, + "node_modules/react-draggable": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.5.tgz", + "integrity": "sha512-OMHzJdyJbYTZo4uQE393fHcqqPYsEtkjfMgvCHr6rejT+Ezn4OZbNyGH50vv+SunC1RMvwOTSWkEODQLzw1M9g==", + "dependencies": { + "clsx": "^1.1.1", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": ">= 16.3.0", + "react-dom": ">= 16.3.0" + } + }, "node_modules/react-dropzone": { "version": "14.2.3", "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz", @@ -5177,6 +5201,18 @@ "node": ">=0.10.0" } }, + "node_modules/react-resizable": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.5.tgz", + "integrity": "sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w==", + "dependencies": { + "prop-types": "15.x", + "react-draggable": "^4.0.3" + }, + "peerDependencies": { + "react": ">= 16.3" + } + }, "node_modules/react-router": { "version": "6.8.1", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.1.tgz", @@ -7254,6 +7290,15 @@ "@types/react": "*" } }, + "@types/react-resizable": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/react-resizable/-/react-resizable-3.0.4.tgz", + "integrity": "sha512-+QguN9CDfC1lthq+4noG1fkxh8cqkV2Fv/Mu3mdknCCBiwwNLecnBdk1MmNNN7uJpT23Nx/aVkYsbt5NuWouFw==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/react-syntax-highlighter": { "version": "15.5.6", "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.6.tgz", @@ -9507,6 +9552,15 @@ "scheduler": "^0.23.0" } }, + "react-draggable": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.5.tgz", + "integrity": "sha512-OMHzJdyJbYTZo4uQE393fHcqqPYsEtkjfMgvCHr6rejT+Ezn4OZbNyGH50vv+SunC1RMvwOTSWkEODQLzw1M9g==", + "requires": { + "clsx": "^1.1.1", + "prop-types": "^15.8.1" + } + }, "react-dropzone": { "version": "14.2.3", "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz", @@ -9586,6 +9640,15 @@ "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", "dev": true }, + "react-resizable": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.5.tgz", + "integrity": "sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w==", + "requires": { + "prop-types": "15.x", + "react-draggable": "^4.0.3" + } + }, "react-router": { "version": "6.8.1", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.1.tgz", diff --git a/src/chainlit/frontend/package.json b/src/chainlit/frontend/package.json index f500180b0a..fc6aad7fda 100644 --- a/src/chainlit/frontend/package.json +++ b/src/chainlit/frontend/package.json @@ -28,6 +28,7 @@ "react-hot-toast": "^2.4.0", "react-hotkeys-hook": "^4.4.0", "react-markdown": "^8.0.6", + "react-resizable": "^3.0.5", "react-router-dom": "^6.8.1", "react-string-replace": "^1.1.0", "react-syntax-highlighter": "^15.5.0", @@ -45,6 +46,7 @@ "@types/node": "^20.0.0", "@types/react": "^18.0.27", "@types/react-dom": "^18.0.10", + "@types/react-resizable": "^3.0.4", "@types/react-syntax-highlighter": "^15.5.6", "@types/react-window-infinite-loader": "^1.0.6", "@typescript-eslint/eslint-plugin": "^5.59.2", diff --git a/src/chainlit/frontend/src/components/element/sideView.tsx b/src/chainlit/frontend/src/components/element/sideView.tsx index 6751ca2884..e2e2793819 100644 --- a/src/chainlit/frontend/src/components/element/sideView.tsx +++ b/src/chainlit/frontend/src/components/element/sideView.tsx @@ -1,40 +1,55 @@ import CloseIcon from '@mui/icons-material/Close'; import { IconButton, Box, BoxProps, Typography, Stack } from '@mui/material'; -import { styled, Theme, CSSObject } from '@mui/material/styles'; +import { styled } from '@mui/material/styles'; import { renderElement } from 'components/element/view'; -import { useMemo } from 'react'; +import { forwardRef, useMemo, useState } from 'react'; import { useRecoilState } from 'recoil'; import { sideViewState } from 'state/element'; - -const drawerWidth = 400; - -const openedMixin = (theme: Theme): CSSObject => ({ - padding: '1.5rem', - paddingTop: '.5rem', - width: drawerWidth, - transition: theme.transitions.create('transform', { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.enteringScreen - }), - overflowX: 'hidden' -}); - -const closedMixin = (theme: Theme): CSSObject => ({ - transition: theme.transitions.create('transform', { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.leavingScreen - }), - overflowX: 'hidden', - width: 0 -}); +import { Resizable } from 'react-resizable'; +import 'react-resizable/css/styles.css'; interface DrawerProps extends BoxProps { open?: boolean; + width?: number | string; } +const Handle = forwardRef(function Handle( + { ...props }: { handleAxis?: string }, + ref +) { + // Make sure the dom element doesn't receive the handleAxis prop. + delete props.handleAxis; + return ( + + + + ); +}); + const Drawer = styled(Box, { shouldForwardProp: (prop) => prop !== 'open' -})(({ theme, open }) => ({ +})(({ theme, open, width }) => ({ + width, backgroundColor: theme.palette.mode === 'dark' ? theme.palette.grey[800] @@ -44,23 +59,26 @@ const Drawer = styled(Box, { ? theme.palette.grey[800] : theme.palette.grey[200] }`, - display: 'flex', + display: open ? 'flex' : 'none', flexDirection: 'column', borderRadius: 0, flexShrink: 0, color: theme.palette.text.primary, whiteSpace: 'nowrap', boxSizing: 'border-box', - ...(open && { - ...openedMixin(theme) - }), - ...(!open && { - ...closedMixin(theme) - }) + padding: '1.5rem', + paddingTop: '.5rem', + overflowX: 'hidden' })); const SideView = () => { const [sideViewElement, setSideViewElement] = useRecoilState(sideViewState); + const [resizeInProgress, setResizeInProgress] = useState(false); + const [drawerWidth, setDrawerWidth] = useState(400); + + const handleResize = (event: any, data: { size: { width: number } }) => { + setDrawerWidth(data.size.width); + }; const element = useMemo(() => { if (sideViewElement) { @@ -70,35 +88,49 @@ const SideView = () => { }, [sideViewElement]); return ( - - - - {sideViewElement?.name} - - setSideViewElement(undefined)} - > - - - + setResizeInProgress(true)} + onResizeStop={() => setResizeInProgress(false)} + resizeHandles={['w']} + handle={} + axis="x" + minConstraints={[100, 0]} // Minimum width of 100px and no limit on height. + maxConstraints={[1000, 0]} // Maximum width of 1000px and no limit on height. + > + + + + {sideViewElement?.name} + + setSideViewElement(undefined)} + > + + + - - {element} - - + + {element} + + + ); };