diff --git a/packages/desktop-client/package.json b/packages/desktop-client/package.json index 9dcbf85b9a1c50345dba7d142801352dfae31375..d7a7de8623bc7a3e2bc1b485325168c939514f10 100644 --- a/packages/desktop-client/package.json +++ b/packages/desktop-client/package.json @@ -47,6 +47,7 @@ "memoize-one": "^6.0.0", "pikaday": "1.8.2", "promise-retry": "^2.0.1", + "re-resizable": "^6.9.17", "react": "18.2.0", "react-aria-components": "^1.2.1", "react-dnd": "^16.0.1", diff --git a/packages/desktop-client/src/components/sidebar/Sidebar.tsx b/packages/desktop-client/src/components/sidebar/Sidebar.tsx index 77e863ccf18d3f9166ab82a6b37a536299b059d6..f39629292af86df706218a96b6f92c91dac8ae6d 100644 --- a/packages/desktop-client/src/components/sidebar/Sidebar.tsx +++ b/packages/desktop-client/src/components/sidebar/Sidebar.tsx @@ -1,6 +1,8 @@ import React, { useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; +import { Resizable } from 're-resizable'; + import { closeBudget, moveAccount, @@ -12,9 +14,11 @@ import { useAccounts } from '../../hooks/useAccounts'; import { useGlobalPref } from '../../hooks/useGlobalPref'; import { useLocalPref } from '../../hooks/useLocalPref'; import { useNavigate } from '../../hooks/useNavigate'; +import { useResizeObserver } from '../../hooks/useResizeObserver'; import { SvgExpandArrow } from '../../icons/v0'; import { SvgReports, SvgWallet } from '../../icons/v1'; import { SvgCalendar } from '../../icons/v2'; +import { useResponsive } from '../../ResponsiveProvider'; import { styles, theme } from '../../style'; import { Button } from '../common/Button'; import { InitialFocus } from '../common/InitialFocus'; @@ -30,20 +34,28 @@ import { useSidebar } from './SidebarProvider'; import { ToggleButton } from './ToggleButton'; import { Tools } from './Tools'; -export const SIDEBAR_WIDTH = 240; - export function Sidebar() { const hasWindowButtons = !Platform.isBrowser && Platform.OS === 'mac'; const dispatch = useDispatch(); const sidebar = useSidebar(); const accounts = useAccounts(); + const { width } = useResponsive(); const [showClosedAccounts, setShowClosedAccountsPref] = useLocalPref( 'ui.showClosedAccounts', ); const [isFloating = false, setFloatingSidebarPref] = useGlobalPref('floatingSidebar'); + const [_sidebarWidth, setSidebarWidth] = useLocalPref('sidebarWidth'); + const DEFAULT_SIDEBAR_WIDTH = 240; + const MAX_SIDEBAR_WIDTH = width / 3; + const MIN_SIDEBAR_WIDTH = 200; + const sidebarWidth = Math.min( + MAX_SIDEBAR_WIDTH, + Math.max(MIN_SIDEBAR_WIDTH, _sidebarWidth || DEFAULT_SIDEBAR_WIDTH), + ); + async function onReorder( id: string, dropPos: 'top' | 'bottom', @@ -70,72 +82,96 @@ export function Sidebar() { setShowClosedAccountsPref(!showClosedAccounts); }; + const containerRef = useResizeObserver(rect => { + setSidebarWidth(rect.width); + }); + return ( - <View - style={{ - width: SIDEBAR_WIDTH, - color: theme.sidebarItemText, - backgroundColor: theme.sidebarBackground, - '& .float': { - opacity: isFloating ? 1 : 0, - transition: 'opacity .25s, width .25s', - width: hasWindowButtons || isFloating ? null : 0, - }, - '&:hover .float': { - opacity: 1, - width: hasWindowButtons ? null : 'auto', - }, - flex: 1, - ...styles.darkScrollbar, + <Resizable + defaultSize={{ + width: sidebarWidth, + height: '100%', + }} + maxWidth={MAX_SIDEBAR_WIDTH} + minWidth={MIN_SIDEBAR_WIDTH} + enable={{ + top: false, + right: true, + bottom: false, + left: false, + topRight: false, + bottomRight: false, + bottomLeft: false, + topLeft: false, }} > <View + innerRef={containerRef} style={{ - paddingTop: 35, - height: 30, - flexDirection: 'row', - alignItems: 'center', - margin: '0 8px 23px 20px', - transition: 'padding .4s', - ...(hasWindowButtons && { - paddingTop: 20, - justifyContent: 'flex-start', - }), + color: theme.sidebarItemText, + height: '100%', + backgroundColor: theme.sidebarBackground, + '& .float': { + opacity: isFloating ? 1 : 0, + transition: 'opacity .25s, width .25s', + width: hasWindowButtons || isFloating ? null : 0, + }, + '&:hover .float': { + opacity: 1, + width: hasWindowButtons ? null : 'auto', + }, + flex: 1, + ...styles.darkScrollbar, }} > - <EditableBudgetName /> + <View + style={{ + paddingTop: 35, + height: 30, + flexDirection: 'row', + alignItems: 'center', + margin: '0 8px 23px 20px', + transition: 'padding .4s', + ...(hasWindowButtons && { + paddingTop: 20, + justifyContent: 'flex-start', + }), + }} + > + <EditableBudgetName /> - <View style={{ flex: 1, flexDirection: 'row' }} /> + <View style={{ flex: 1, flexDirection: 'row' }} /> - {!sidebar.alwaysFloats && ( - <ToggleButton isFloating={isFloating} onFloat={onFloat} /> - )} - </View> + {!sidebar.alwaysFloats && ( + <ToggleButton isFloating={isFloating} onFloat={onFloat} /> + )} + </View> - <View style={{ overflow: 'auto' }}> - <Item title="Budget" Icon={SvgWallet} to="/budget" /> - <Item title="Reports" Icon={SvgReports} to="/reports" /> + <View style={{ overflow: 'auto' }}> + <Item title="Budget" Icon={SvgWallet} to="/budget" /> + <Item title="Reports" Icon={SvgReports} to="/reports" /> - <Item title="Schedules" Icon={SvgCalendar} to="/schedules" /> + <Item title="Schedules" Icon={SvgCalendar} to="/schedules" /> - <Tools /> + <Tools /> - <View - style={{ - height: 1, - backgroundColor: theme.sidebarItemBackgroundHover, - marginTop: 15, - flexShrink: 0, - }} - /> + <View + style={{ + height: 1, + backgroundColor: theme.sidebarItemBackgroundHover, + marginTop: 15, + flexShrink: 0, + }} + /> - <Accounts - onAddAccount={onAddAccount} - onToggleClosedAccounts={onToggleClosedAccounts} - onReorder={onReorder} - /> + <Accounts + onAddAccount={onAddAccount} + onToggleClosedAccounts={onToggleClosedAccounts} + onReorder={onReorder} + /> + </View> </View> - </View> + </Resizable> ); } diff --git a/packages/desktop-client/src/components/sidebar/index.tsx b/packages/desktop-client/src/components/sidebar/index.tsx index 59f7b1628ad86e58eb71e1e735b8944694107e7e..ba5185138cc9b9459403e91d48f1f12e8057f15f 100644 --- a/packages/desktop-client/src/components/sidebar/index.tsx +++ b/packages/desktop-client/src/components/sidebar/index.tsx @@ -4,7 +4,7 @@ import { useGlobalPref } from '../../hooks/useGlobalPref'; import { useResponsive } from '../../ResponsiveProvider'; import { View } from '../common/View'; -import { SIDEBAR_WIDTH, Sidebar } from './Sidebar'; +import { Sidebar } from './Sidebar'; import { useSidebar } from './SidebarProvider'; export function FloatableSidebar() { @@ -42,10 +42,8 @@ export function FloatableSidebar() { : '0 15px 30px 0 rgba(0,0,0,0.25), 0 3px 15px 0 rgba(0,0,0,.5)', transform: `translateY(${!sidebarShouldFloat ? -12 : 0}px) translateX(${ - sidebarShouldFloat && sidebar.hidden - ? -SIDEBAR_WIDTH - : 0 - }px)`, + sidebarShouldFloat && sidebar.hidden ? '-100' : '0' + }%)`, transition: 'transform .5s, box-shadow .5s, border-radius .5s, bottom .5s', }} diff --git a/packages/loot-core/src/types/prefs.d.ts b/packages/loot-core/src/types/prefs.d.ts index 81c51951c00234d2ef34b743da3adc0fa1353088..2ce56c31c769999667ccab719885b958ba5149bb 100644 --- a/packages/loot-core/src/types/prefs.d.ts +++ b/packages/loot-core/src/types/prefs.d.ts @@ -53,6 +53,7 @@ export type LocalPrefs = Partial< reportsViewLegend: boolean; reportsViewSummary: boolean; reportsViewLabel: boolean; + sidebarWidth: number; 'mobile.showSpentColumn': boolean; } & Record<`flags.${FeatureFlag}`, boolean> >; diff --git a/upcoming-release-notes/2993.md b/upcoming-release-notes/2993.md new file mode 100644 index 0000000000000000000000000000000000000000..bf7643988784cec1cd43896510404e952941a1a8 --- /dev/null +++ b/upcoming-release-notes/2993.md @@ -0,0 +1,6 @@ +--- +category: Features +authors: [YusefOuda] +--- + +Adds ability to resize sidebar. diff --git a/yarn.lock b/yarn.lock index 3261a182b74a4b5e53911498db09506ab4897918..ac628b4f5d6f490e30b6fbbe4efe070e7fd7f0f5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -100,6 +100,7 @@ __metadata: memoize-one: "npm:^6.0.0" pikaday: "npm:1.8.2" promise-retry: "npm:^2.0.1" + re-resizable: "npm:^6.9.17" react: "npm:18.2.0" react-aria-components: "npm:^1.2.1" react-dnd: "npm:^16.0.1" @@ -15469,6 +15470,16 @@ __metadata: languageName: node linkType: hard +"re-resizable@npm:^6.9.17": + version: 6.9.17 + resolution: "re-resizable@npm:6.9.17" + peerDependencies: + react: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 + checksum: 768c3a0fe39d6916caf4e003240d326d62c4d7512c7d3115cc2a98085416fdba80097afdbb93df57b69543c41ce56b33589f2fea6987cd5149faa83cf11c8ba1 + languageName: node + linkType: hard + "react-aria-components@npm:^1.2.1": version: 1.2.1 resolution: "react-aria-components@npm:1.2.1"