Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Make the width of categories column in the budget view customizable #3445

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @ts-strict-ignore
import React, { type ComponentProps, memo } from 'react';

import { useLocalPref } from '../../hooks/useLocalPref';
import { View } from '../common/View';

import { MonthPicker } from './MonthPicker';
Expand All @@ -15,8 +16,10 @@ type BudgetPageHeaderProps = {

export const BudgetPageHeader = memo<BudgetPageHeaderProps>(
({ startMonth, onMonthSelect, numMonths, monthBounds }) => {
const [categoryWidth = 200] = useLocalPref('category.width');

return (
<View style={{ marginLeft: 200 + 5, flexShrink: 0 }}>
<View style={{ marginLeft: categoryWidth + 5, flexShrink: 0 }}>
<View style={{ marginRight: 5 + getScrollbarWidth() }}>
<MonthPicker
startMonth={startMonth}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export function BudgetTable(props) {
onBudgetAction,
} = props;

const [categoryWidth = 200] = useLocalPref('category.width');
const { grouped: categoryGroups } = useCategories();
const [collapsedGroupIds = [], setCollapsedGroupIdsPref] =
useLocalPref('budget.collapsed');
Expand Down Expand Up @@ -183,7 +184,7 @@ export function BudgetTable(props) {
paddingRight: 5 + getScrollbarWidth(),
}}
>
<View style={{ width: 200 }} />
<View style={{ width: categoryWidth }} />
<MonthsProvider
startMonth={prewarmStartMonth}
numMonths={numMonths}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { type ComponentProps, memo, useRef, useState } from 'react';
import { useTranslation, Trans } from 'react-i18next';

import { useLocalPref } from '../../hooks/useLocalPref';
import { SvgDotsHorizontalTriple } from '../../icons/v1';
import { theme, styles } from '../../style';
import { Button } from '../common/Button2';
Expand All @@ -25,6 +26,7 @@ export const BudgetTotals = memo(function BudgetTotals({
collapseAllCategories,
}: BudgetTotalsProps) {
const { t } = useTranslation();
const [categoryWidth = 200] = useLocalPref('category.width');
const [menuOpen, setMenuOpen] = useState(false);
const triggerRef = useRef(null);

Expand All @@ -44,7 +46,7 @@ export const BudgetTotals = memo(function BudgetTotals({
>
<View
style={{
width: 200,
width: categoryWidth,
color: theme.pageTextLight,
justifyContent: 'center',
paddingLeft: 15,
Expand Down Expand Up @@ -77,7 +79,7 @@ export const BudgetTotals = memo(function BudgetTotals({
triggerRef={triggerRef}
isOpen={menuOpen}
onOpenChange={() => setMenuOpen(false)}
style={{ width: 200 }}
style={{ width: categoryWidth }}
>
<Menu
onMenuSelect={type => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import AutoSizer from 'react-virtualized-auto-sizer';

import * as monthUtils from 'loot-core/src/shared/months';

import { useLocalPref } from '../../hooks/useLocalPref';
import { View } from '../common/View';

import { useBudgetMonthCount } from './BudgetMonthCountContext';
import { BudgetPageHeader } from './BudgetPageHeader';
import { BudgetTable } from './BudgetTable';

function getNumPossibleMonths(width: number) {
const estimatedTableWidth = width - 200;
const estimatedTableWidth = width;

if (estimatedTableWidth < 500) {
return 1;
Expand Down Expand Up @@ -46,10 +47,10 @@ const DynamicBudgetTableInner = ({
...props
}: DynamicBudgetTableInnerProps) => {
const { setDisplayMax } = useBudgetMonthCount();

const numPossible = getNumPossibleMonths(width);
const [categoryWidth = 200] = useLocalPref('category.width');
const numPossible = getNumPossibleMonths(width - categoryWidth);
const numMonths = Math.min(numPossible, maxMonths);
const maxWidth = 200 + 500 * numMonths;
const maxWidth = categoryWidth + 500 * numMonths;

useEffect(() => {
setDisplayMax(numPossible);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
type CategoryEntity,
} from 'loot-core/src/types/models';

import { useLocalPref } from '../../hooks/useLocalPref';
import { SvgCheveronDown } from '../../icons/v1';
import { theme } from '../../style';
import { Button } from '../common/Button2';
Expand Down Expand Up @@ -47,7 +48,7 @@ export function SidebarCategory({
onHideNewCategory,
}: SidebarCategoryProps) {
const { t } = useTranslation();

const [categoryWidth = 200] = useLocalPref('category.width');
const temporary = category.id === 'new';
const [menuOpen, setMenuOpen] = useState(false);
const triggerRef = useRef(null);
Expand Down Expand Up @@ -93,7 +94,7 @@ export function SidebarCategory({
placement="bottom start"
isOpen={menuOpen}
onOpenChange={() => setMenuOpen(false)}
style={{ width: 200 }}
style={{ width: categoryWidth }}
>
<Menu
onMenuSelect={type => {
Expand Down Expand Up @@ -132,7 +133,7 @@ export function SidebarCategory({
<View
innerRef={innerRef}
style={{
width: 200,
width: categoryWidth,
overflow: 'hidden',
'& .hover-visible': {
display: 'none',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, { type CSSProperties, useRef, useState } from 'react';
import { type ConnectDragSource } from 'react-dnd';
import { useTranslation } from 'react-i18next';

import { useLocalPref } from '../../hooks/useLocalPref';
import { SvgExpandArrow } from '../../icons/v0';
import { SvgCheveronDown } from '../../icons/v1';
import { theme } from '../../style';
Expand Down Expand Up @@ -52,7 +53,7 @@ export function SidebarGroup({
onToggleCollapse,
}: SidebarGroupProps) {
const { t } = useTranslation();

const [categoryWidth = 200] = useLocalPref('category.width');
const temporary = group.id === 'new';
const [menuOpen, setMenuOpen] = useState(false);
const triggerRef = useRef(null);
Expand Down Expand Up @@ -110,7 +111,7 @@ export function SidebarGroup({
placement="bottom start"
isOpen={menuOpen}
onOpenChange={() => setMenuOpen(false)}
style={{ width: 200 }}
style={{ width: categoryWidth }}
>
<Menu
onMenuSelect={type => {
Expand Down Expand Up @@ -155,7 +156,7 @@ export function SidebarGroup({
innerRef={innerRef}
style={{
...style,
width: 200,
width: categoryWidth,
backgroundColor: theme.tableRowHeaderBackground,
overflow: 'hidden',
'& .hover-visible': {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { evalArithmetic } from 'loot-core/src/shared/arithmetic';
import * as monthUtils from 'loot-core/src/shared/months';
import { integerToCurrency, amountToInteger } from 'loot-core/src/shared/util';

import { useLocalPref } from '../../../hooks/useLocalPref';
import { useUndo } from '../../../hooks/useUndo';
import { SvgCheveronDown } from '../../../icons/v1';
import { styles, theme, type CSSProperties } from '../../../style';
Expand Down Expand Up @@ -192,6 +193,7 @@ export const ExpenseCategoryMonth = memo(function ExpenseCategoryMonth({
onBudgetAction,
onShowActivity,
}: ExpenseCategoryMonthProps) {
const [categoryWidth = 200] = useLocalPref('category.width');
const budgetMenuTriggerRef = useRef(null);
const balanceMenuTriggerRef = useRef(null);
const [budgetMenuOpen, setBudgetMenuOpen] = useState(false);
Expand Down Expand Up @@ -261,7 +263,7 @@ export const ExpenseCategoryMonth = memo(function ExpenseCategoryMonth({
placement="bottom start"
isOpen={budgetMenuOpen}
onOpenChange={() => setBudgetMenuOpen(false)}
style={{ width: 200 }}
style={{ width: categoryWidth }}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Action Required: Hardcoded width detected in IncomeHeader.tsx

The search revealed that width: 200 is still hardcoded in the following file:

  • packages/desktop-client/src/components/budget/IncomeHeader.tsx

Please replace the hardcoded value with the categoryWidth variable to maintain consistency and support dynamic width adjustment.

🔗 Analysis chain

LGTM: Consistent usage of categoryWidth in Popover components.

The categoryWidth variable is correctly used to set the width of the Popover components, replacing the hardcoded value of 200. This change successfully implements the customizable width feature as per the PR objectives.

To ensure consistency across the codebase:

If the script returns any results, please review those occurrences to determine if they should also use the categoryWidth variable.

Also applies to: 392-392

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify that all occurrences of hardcoded width 200 for categories have been replaced
rg --type typescript "width: 200" packages/desktop-client/src/components/budget

Length of output: 116


Script:

#!/bin/bash
# Corrected script to verify that all occurrences of hardcoded width 200 for categories have been replaced
rg --type tsx "width: 200" packages/desktop-client/src/components/budget

Length of output: 102


Script:

#!/bin/bash
# Verify that all occurrences of hardcoded width 200 for categories have been replaced using a glob pattern
rg --glob "*.tsx" "width: 200" packages/desktop-client/src/components/budget

Length of output: 163

>
<BudgetMenu
onCopyLastMonthAverage={() => {
Expand Down Expand Up @@ -387,7 +389,7 @@ export const ExpenseCategoryMonth = memo(function ExpenseCategoryMonth({
placement="bottom end"
isOpen={balanceMenuOpen}
onOpenChange={() => setBalanceMenuOpen(false)}
style={{ width: 200 }}
style={{ width: categoryWidth }}
>
<BalanceMovementMenu
categoryId={category.id}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useRef, useState } from 'react';

import { rolloverBudget } from 'loot-core/src/client/queries';

import { useLocalPref } from '../../../../hooks/useLocalPref';
import { type CSSProperties } from '../../../../style';
import { Popover } from '../../../common/Popover';
import { View } from '../../../common/View';
Expand Down Expand Up @@ -29,6 +30,7 @@ export function ToBudget({
amountStyle,
isCollapsed = false,
}: ToBudgetProps) {
const [categoryWidth = 200] = useLocalPref('category.width');
const [menuOpen, setMenuOpen] = useState<string | null>(null);
const triggerRef = useRef(null);
const sheetValue = useRolloverSheetValue({
Expand Down Expand Up @@ -60,7 +62,7 @@ export function ToBudget({
placement="bottom"
isOpen={isMenuOpen}
onOpenChange={() => setMenuOpen(null)}
style={{ width: 200 }}
style={{ width: categoryWidth }}
>
{menuOpen === 'actions' && (
<ToBudgetMenu
Expand Down
68 changes: 68 additions & 0 deletions packages/desktop-client/src/components/settings/Display.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { useState, type HTMLProps } from 'react';
import { Trans } from 'react-i18next';

import { useLocalPref } from '../../hooks/useLocalPref';
import { theme as themeStyle } from '../../style';
import { Input } from '../common/Input';
import { Text } from '../common/Text';
import { View } from '../common/View';

import { Column, Setting } from './UI';

export function DisplaySettings() {
const [prefWidth = 200, setPrefWidth] = useLocalPref('category.width');
const [tempWidth, setTempWidth] = useState(prefWidth.toString());

const onBlur: HTMLProps<HTMLInputElement>['onBlur'] = event => {
if (document.hasFocus()) {
const value = parseInt(event.target.value);

if (Number.isInteger(value)) {
const clampedValue = Math.max(100, Math.min(1024, value));
setPrefWidth(clampedValue);
setTempWidth(clampedValue.toString());
} else {
setTempWidth(prefWidth.toString());
}
}
};
Comment on lines +16 to +28
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider refactoring the onBlur handler for improved readability and reusability.

While the logic is correct, consider the following improvements:

  1. Extract the clamping logic into a separate function for reusability.
  2. Add explicit error handling for non-integer inputs.
  3. Consider using a custom hook to encapsulate this logic.

Here's a suggested refactoring:

const clampWidth = (value: number) => Math.max(100, Math.min(1024, value));

const useWidthInput = (initialWidth: number) => {
  const [width, setWidth] = useState(initialWidth);
  const [tempWidth, setTempWidth] = useState(initialWidth.toString());

  const handleBlur = (event: React.FocusEvent<HTMLInputElement>) => {
    if (document.hasFocus()) {
      const value = parseInt(event.target.value, 10);
      if (Number.isInteger(value)) {
        const clampedValue = clampWidth(value);
        setWidth(clampedValue);
        setTempWidth(clampedValue.toString());
      } else {
        setTempWidth(width.toString());
      }
    }
  };

  return { width, tempWidth, setTempWidth, handleBlur };
};

// In your component:
const { width, tempWidth, setTempWidth, handleBlur } = useWidthInput(prefWidth);

This refactoring improves readability, reusability, and separation of concerns.


return (
<Setting
primaryAction={
<View
style={{
flexDirection: 'column',
gap: '1em',
width: '100%',
}}
>
<Column title="Categories Width">
<Text>
<Trans>
Width of the categories column in pixels. Must be between
</Trans>
</Text>
<Input
value={tempWidth}
onChange={event => setTempWidth(event.target.value)}
onBlur={onBlur}
style={{
':hover': {
backgroundColor: themeStyle.buttonNormalBackgroundHover,
},
}}
/>
</Column>
</View>
}
>
<Text>
<Trans>
<strong>Display settings</strong> change how certain elements of the
interface are displayed.
</Trans>
</Text>
</Setting>
);
}
Comment on lines +30 to +68
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider extracting styles and improving component structure.

The component structure is well-organized, but there are a few areas for improvement:

  1. Extract inline styles into a separate stylesheet or use styled-components for better maintainability.
  2. Consider breaking down the component into smaller, reusable parts.
  3. The use of Trans for internationalization is good, but ensure all user-facing strings are wrapped with it.

Here's a suggestion for extracting styles:

import styled from 'styled-components';

const StyledInput = styled(Input)`
  &:hover {
    background-color: ${props => props.theme.buttonNormalBackgroundHover};
  }
`;

const SettingContainer = styled(View)`
  flex-direction: column;
  gap: 1em;
  width: 100%;
`;

// Then in your JSX:
<SettingContainer>
  <Column title={<Trans>Categories Width</Trans>}>
    {/* ... */}
    <StyledInput
      value={tempWidth}
      onChange={event => setTempWidth(event.target.value)}
      onBlur={handleBlur}
    />
  </Column>
</SettingContainer>

This approach improves code readability and maintainability by separating concerns.

18 changes: 1 addition & 17 deletions packages/desktop-client/src/components/settings/Format.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { View } from '../common/View';
import { Checkbox } from '../forms';
import { useSidebar } from '../sidebar/SidebarProvider';

import { Setting } from './UI';
import { Column, Setting } from './UI';

// Follows Pikaday 'firstDay' numbering
// https://github.com/Pikaday/Pikaday
Expand All @@ -37,22 +37,6 @@ const dateFormats: { value: SyncedPrefs['dateFormat']; label: string }[] = [
{ value: 'dd.MM.yyyy', label: 'DD.MM.YYYY' },
];

function Column({ title, children }: { title: string; children: ReactNode }) {
return (
<View
style={{
alignItems: 'flex-start',
flexGrow: 1,
gap: '0.5em',
width: '100%',
}}
>
<Text style={{ fontWeight: 500 }}>{title}</Text>
<View style={{ alignItems: 'flex-start', gap: '1em' }}>{children}</View>
</View>
);
}

export function FormatSettings() {
const sidebar = useSidebar();
const [_firstDayOfWeekIdx, setFirstDayOfWeekIdxPref] =
Expand Down
18 changes: 1 addition & 17 deletions packages/desktop-client/src/components/settings/Themes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,7 @@ import { Text } from '../common/Text';
import { View } from '../common/View';
import { useSidebar } from '../sidebar/SidebarProvider';

import { Setting } from './UI';

function Column({ title, children }: { title: string; children: ReactNode }) {
return (
<View
style={{
alignItems: 'flex-start',
flexGrow: 1,
gap: '0.5em',
width: '100%',
}}
>
<Text style={{ fontWeight: 500 }}>{title}</Text>
<View style={{ alignItems: 'flex-start', gap: '1em' }}>{children}</View>
</View>
);
}
import { Column, Setting } from './UI';

export function ThemeSettings() {
const sidebar = useSidebar();
Expand Down
22 changes: 22 additions & 0 deletions packages/desktop-client/src/components/settings/UI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,30 @@ import { css, media } from 'glamor';
import { type CSSProperties, theme } from '../../style';
import { tokens } from '../../tokens';
import { Link } from '../common/Link';
import { Text } from '../common/Text';
import { View } from '../common/View';

type ColumProps = {
title: string;
children: ReactNode;
};

export function Column({ title, children }: ColumProps) {
return (
<View
style={{
alignItems: 'flex-start',
flexGrow: 1,
gap: '0.5em',
width: '100%',
}}
>
<Text style={{ fontWeight: 500 }}>{title}</Text>
<View style={{ alignItems: 'flex-start', gap: '1em' }}>{children}</View>
</View>
);
}

type SettingProps = {
primaryAction?: ReactNode;
style?: CSSProperties;
Expand Down
Loading
Loading