From 4f35333a39d5ed7d780b37d067e3519ed5ffe40e Mon Sep 17 00:00:00 2001 From: ilkin-overleaf <100852799+ilkin-overleaf@users.noreply.github.com> Date: Mon, 9 Jan 2023 16:33:14 +0200 Subject: [PATCH] Merge pull request #9536 from overleaf/ii-adjustable-project-dashboard-panel [web] Adjustable project dashboard sidebar GitOrigin-RevId: 1007ecb896bbe215af28fa92d295201b2457aeef --- .../components/project-list-root.tsx | 15 +-- .../components/sidebar/sidebar.tsx | 45 +++++++ .../frontend/js/shared/hooks/use-resize.ts | 113 +++++++++++++++++ .../stylesheets/app/project-list-react.less | 4 + .../frontend/shared/hooks/use-resize.spec.tsx | 117 ++++++++++++++++++ 5 files changed, 281 insertions(+), 13 deletions(-) create mode 100644 services/web/frontend/js/features/project-list/components/sidebar/sidebar.tsx create mode 100644 services/web/frontend/js/shared/hooks/use-resize.ts create mode 100644 services/web/test/frontend/shared/hooks/use-resize.spec.tsx diff --git a/services/web/frontend/js/features/project-list/components/project-list-root.tsx b/services/web/frontend/js/features/project-list/components/project-list-root.tsx index 1205aecd7c..28e845f1ec 100644 --- a/services/web/frontend/js/features/project-list/components/project-list-root.tsx +++ b/services/web/frontend/js/features/project-list/components/project-list-root.tsx @@ -9,8 +9,6 @@ import useWaitForI18n from '../../../shared/hooks/use-wait-for-i18n' import CurrentPlanWidget from './current-plan-widget/current-plan-widget' import NewProjectButton from './new-project-button' import ProjectListTable from './table/project-list-table' -import SidebarFilters from './sidebar/sidebar-filters' -import AddAffiliation, { useAddAffiliation } from './sidebar/add-affiliation' import SurveyWidget from './survey-widget' import WelcomeMessage from './welcome-message' import LoadingBranded from '../../../shared/components/loading-branded' @@ -20,6 +18,7 @@ import SearchForm from './search-form' import ProjectsDropdown from './dropdown/projects-dropdown' import SortByDropdown from './dropdown/sort-by-dropdown' import ProjectTools from './table/project-tools/project-tools' +import Sidebar from './sidebar/sidebar' import LoadMore from './load-more' import { useEffect } from 'react' @@ -43,7 +42,6 @@ function ProjectListPageContent() { setSearchText, selectedProjects, } = useProjectListContext() - const { show: showAddAffiliationWidget } = useAddAffiliation() useEffect(() => { eventTracking.sendMB('loads_v2_dash', {}) @@ -59,16 +57,7 @@ function ProjectListPageContent() {
{totalProjectsCount > 0 ? ( <> -
-
- -
-
+
{error ? : ''} diff --git a/services/web/frontend/js/features/project-list/components/sidebar/sidebar.tsx b/services/web/frontend/js/features/project-list/components/sidebar/sidebar.tsx new file mode 100644 index 0000000000..42029f94f0 --- /dev/null +++ b/services/web/frontend/js/features/project-list/components/sidebar/sidebar.tsx @@ -0,0 +1,45 @@ +import NewProjectButton from '../new-project-button' +import SidebarFilters from './sidebar-filters' +import AddAffiliation, { useAddAffiliation } from './add-affiliation' +import { usePersistedResize } from '../../../../shared/hooks/use-resize' + +function Sidebar() { + const { show: showAddAffiliationWidget } = useAddAffiliation() + const { mousePos, getHandleProps, getTargetProps } = usePersistedResize({ + name: 'project-sidebar', + }) + + return ( +
+
+ +
+
+
+ ) +} + +export default Sidebar diff --git a/services/web/frontend/js/shared/hooks/use-resize.ts b/services/web/frontend/js/shared/hooks/use-resize.ts new file mode 100644 index 0000000000..4524b90af7 --- /dev/null +++ b/services/web/frontend/js/shared/hooks/use-resize.ts @@ -0,0 +1,113 @@ +import { useState, useEffect, useRef } from 'react' +import usePersistedState from './use-persisted-state' +import { Nullable } from '../../../../types/utils' + +type Pos = Nullable<{ + x: number +}> + +function useResizeBase( + state: [Pos, React.Dispatch>] +) { + const [mousePos, setMousePos] = state + const isResizingRef = useRef(false) + const handleRef = useRef(null) + const defaultHandleStyles = useRef({ + cursor: 'col-resize', + userSelect: 'none', + }) + + useEffect(() => { + const handleMouseDown = function (e: MouseEvent) { + if (e.button !== 0) { + return + } + + if (defaultHandleStyles.current.cursor) { + document.body.style.cursor = defaultHandleStyles.current.cursor + } + + isResizingRef.current = true + } + + const handle = handleRef.current + handle?.addEventListener('mousedown', handleMouseDown) + + return () => { + handle?.removeEventListener('mousedown', handleMouseDown) + } + }, []) + + useEffect(() => { + const handleMouseUp = function () { + document.body.style.cursor = 'default' + isResizingRef.current = false + } + + document.addEventListener('mouseup', handleMouseUp) + + return () => { + document.removeEventListener('mouseup', handleMouseUp) + } + }, []) + + useEffect(() => { + const handleMouseMove = function (e: MouseEvent) { + if (isResizingRef.current) { + setMousePos({ x: e.clientX }) + } + } + + document.addEventListener('mousemove', handleMouseMove) + + return () => { + document.removeEventListener('mousemove', handleMouseMove) + } + }, [setMousePos]) + + const getTargetProps = ({ style }: { style?: React.CSSProperties } = {}) => { + return { + style: { + ...style, + }, + } + } + + const setHandleRef = (node: HTMLElement | null) => { + handleRef.current = node + } + + const getHandleProps = ({ style }: { style?: React.CSSProperties } = {}) => { + if (style?.cursor) { + defaultHandleStyles.current.cursor = style.cursor + } + + return { + style: { + ...defaultHandleStyles.current, + ...style, + }, + ref: setHandleRef, + } + } + + return { + mousePos, + getHandleProps, + getTargetProps, + } +} + +function useResize() { + const state = useState(null) + + return useResizeBase(state) +} + +function usePersistedResize({ name }: { name: string }) { + const state = usePersistedState(`resizeable-${name}`, null) + + return useResizeBase(state) +} + +export { useResize, usePersistedResize } diff --git a/services/web/frontend/stylesheets/app/project-list-react.less b/services/web/frontend/stylesheets/app/project-list-react.less index 348e57e4a0..a66fe572cc 100644 --- a/services/web/frontend/stylesheets/app/project-list-react.less +++ b/services/web/frontend/stylesheets/app/project-list-react.less @@ -33,9 +33,12 @@ } .project-list-sidebar-wrapper-react { + position: relative; background-color: @sidebar-bg; flex: @project-list-sidebar-wrapper-flex; min-height: calc(~'100vh -' @header-height); + max-width: 320px; + min-width: 200px; .project-list-sidebar-subwrapper { display: flex; @@ -722,6 +725,7 @@ .project-list-sidebar-survey-wrapper { position: fixed; + z-index: 1; bottom: 0; left: 0; width: 15%; diff --git a/services/web/test/frontend/shared/hooks/use-resize.spec.tsx b/services/web/test/frontend/shared/hooks/use-resize.spec.tsx new file mode 100644 index 0000000000..7ff40394a2 --- /dev/null +++ b/services/web/test/frontend/shared/hooks/use-resize.spec.tsx @@ -0,0 +1,117 @@ +import { + usePersistedResize, + useResize, +} from '../../../../frontend/js/shared/hooks/use-resize' + +function Template({ + mousePos, + getTargetProps, + getHandleProps, +}: ReturnType) { + return ( +
+
+
+ Demo content demo content demo content demo content demo content demo + content +
+
+
+
+ ) +} + +function PersistedResizeTest() { + const props = usePersistedResize({ name: 'test' }) + + return