diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png index 58229d4ae3e4a0aa362e4882381a67302bf34f95..18956272b5608ab7d263cf0da4fa4160f6f7d6b0 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-2-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-2-chromium-linux.png index 278d72136022b255b899859161b5f9763f4d3731..897bc6b1af0963e85a141a95d6c3a5ee96415593 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-2-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-2-chromium-linux.png differ diff --git a/packages/desktop-client/src/components/budget/BudgetCategories.jsx b/packages/desktop-client/src/components/budget/BudgetCategories.jsx index 962697d9a9c05101857713df4eef160de3273470..d6a1a52f79678115223fbdecc028df869b2554c7 100644 --- a/packages/desktop-client/src/components/budget/BudgetCategories.jsx +++ b/packages/desktop-client/src/components/budget/BudgetCategories.jsx @@ -31,11 +31,11 @@ export const BudgetCategories = memo( onReorderCategory, onReorderGroup, }) => { - const [_collapsed, setCollapsedPref] = useLocalPref('budget.collapsed'); - const collapsed = _collapsed || []; + const [collapsedGroupIds = [], setCollapsedGroupIdsPref] = + useLocalPref('budget.collapsed'); const [showHiddenCategories] = useLocalPref('budget.showHiddenCategories'); function onCollapse(value) { - setCollapsedPref(value); + setCollapsedGroupIdsPref(value); } const [isAddingGroup, setIsAddingGroup] = useState(false); @@ -62,13 +62,14 @@ export const BudgetCategories = memo( return [ ...items, - ...(collapsed.includes(group.id) ? [] : groupCategories).map( - cat => ({ - type: 'expense-category', - value: cat, - group, - }), - ), + ...(collapsedGroupIds.includes(group.id) + ? [] + : groupCategories + ).map(cat => ({ + type: 'expense-category', + value: cat, + group, + })), ]; }), ); @@ -83,7 +84,7 @@ export const BudgetCategories = memo( { type: 'income-separator' }, { type: 'income-group', value: incomeGroup }, newCategoryForGroup === incomeGroup.id && { type: 'new-category' }, - ...(collapsed.includes(incomeGroup.id) + ...(collapsedGroupIds.includes(incomeGroup.id) ? [] : incomeGroup.categories.filter( cat => showHiddenCategories || !cat.hidden, @@ -99,7 +100,7 @@ export const BudgetCategories = memo( return items; }, [ categoryGroups, - collapsed, + collapsedGroupIds, newCategoryForGroup, isAddingGroup, showHiddenCategories, @@ -125,7 +126,7 @@ export const BudgetCategories = memo( ...dragState, preview: false, }); - setSavedCollapsed(collapsed); + setSavedCollapsed(collapsedGroupIds); } } else if (state === 'hover') { setDragState({ @@ -140,10 +141,10 @@ export const BudgetCategories = memo( } function onToggleCollapse(id) { - if (collapsed.includes(id)) { - onCollapse(collapsed.filter(id_ => id_ !== id)); + if (collapsedGroupIds.includes(id)) { + onCollapse(collapsedGroupIds.filter(id_ => id_ !== id)); } else { - onCollapse([...collapsed, id]); + onCollapse([...collapsedGroupIds, id]); } } @@ -163,7 +164,7 @@ export const BudgetCategories = memo( } function onShowNewCategory(groupId) { - onCollapse(collapsed.filter(c => c !== groupId)); + onCollapse(collapsedGroupIds.filter(c => c !== groupId)); setNewCategoryForGroup(groupId); } @@ -233,7 +234,7 @@ export const BudgetCategories = memo( <ExpenseGroup group={item.value} editingCell={editingCell} - collapsed={collapsed.includes(item.value.id)} + collapsed={collapsedGroupIds.includes(item.value.id)} MonthComponent={dataComponents.ExpenseGroupComponent} dragState={dragState} onEditName={onEditName} @@ -287,7 +288,7 @@ export const BudgetCategories = memo( group={item.value} editingCell={editingCell} MonthComponent={dataComponents.IncomeGroupComponent} - collapsed={collapsed.includes(item.value.id)} + collapsed={collapsedGroupIds.includes(item.value.id)} onEditName={onEditName} onSave={_onSaveGroup} onToggleCollapse={onToggleCollapse} diff --git a/packages/desktop-client/src/components/budget/BudgetTable.jsx b/packages/desktop-client/src/components/budget/BudgetTable.jsx index f4d45208dce1279046207f58526fcce41911d98d..c9bd7acecbee1a1e9a0524b070edcde0f3862889 100644 --- a/packages/desktop-client/src/components/budget/BudgetTable.jsx +++ b/packages/desktop-client/src/components/budget/BudgetTable.jsx @@ -32,7 +32,8 @@ export function BudgetTable(props) { const budgetCategoriesRef = useRef(); const { grouped: categoryGroups } = useCategories(); - const [collapsed = [], setCollapsedPref] = useLocalPref('budget.collapsed'); + const [collapsedGroupIds = [], setCollapsedGroupIdsPref] = + useLocalPref('budget.collapsed'); const [showHiddenCategories, setShowHiddenCategoriesPef] = useLocalPref( 'budget.showHiddenCategories', ); @@ -95,7 +96,7 @@ export function BudgetTable(props) { const moveVertically = dir => { const flattened = categoryGroups.reduce((all, group) => { - if (collapsed.includes(group.id)) { + if (collapsedGroupIds.includes(group.id)) { return all.concat({ id: group.id, isGroup: true }); } return all.concat([{ id: group.id, isGroup: true }, ...group.categories]); @@ -133,7 +134,7 @@ export function BudgetTable(props) { }; const onCollapse = collapsedIds => { - setCollapsedPref(collapsedIds); + setCollapsedGroupIdsPref(collapsedIds); }; const onToggleHiddenCategories = () => { diff --git a/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx b/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx index c3db205c32ad1bf35989fba477d443dbf3c7239f..952c8f90fd708f8b8223e5c47c1a9b7b801c75e5 100644 --- a/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx +++ b/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx @@ -10,7 +10,13 @@ import * as monthUtils from 'loot-core/src/shared/months'; import { useLocalPref } from '../../../hooks/useLocalPref'; import { useNavigate } from '../../../hooks/useNavigate'; import { SvgLogo } from '../../../icons/logo'; -import { SvgAdd, SvgArrowThinLeft, SvgArrowThinRight } from '../../../icons/v1'; +import { + SvgAdd, + SvgArrowThinLeft, + SvgArrowThinRight, + SvgCheveronDown, + SvgCheveronRight, +} from '../../../icons/v1'; import { useResponsive } from '../../../ResponsiveProvider'; import { theme, styles } from '../../../style'; import { BalanceWithCarryover } from '../../budget/BalanceWithCarryover'; @@ -192,7 +198,7 @@ function ExpenseGroupPreview({ group, pending, style }) { opacity: pending ? 1 : 0.4, }} > - <ExpenseGroupTotals group={group} blank={true} /> + <ExpenseGroupHeader group={group} blank={true} /> {group.categories.map((cat, index) => ( <ExpenseCategory @@ -445,7 +451,7 @@ const ExpenseCategory = memo(function ExpenseCategory({ // </Draggable> }); -const ExpenseGroupTotals = memo(function ExpenseGroupTotals({ +const ExpenseGroupHeader = memo(function ExpenseGroupHeader({ group, budgeted, spent, @@ -455,6 +461,8 @@ const ExpenseGroupTotals = memo(function ExpenseGroupTotals({ blank, show3Cols, showBudgetedCol, + collapsed, + onToggleCollapse, }) { const opacity = blank ? 0 : 1; const listItemRef = useRef(); @@ -466,11 +474,36 @@ const ExpenseGroupTotals = memo(function ExpenseGroupTotals({ alignItems: 'center', backgroundColor: theme.tableRowHeaderBackground, opacity: !!group.hidden ? 0.5 : undefined, + paddingLeft: 0, }} data-testid="totals" innerRef={listItemRef} > - <View role="button" style={{ flex: 1 }}> + <View + role="button" + style={{ + flex: 1, + alignItems: 'center', + flexDirection: 'row', + }} + > + <Button + type="bare" + style={{ margin: '0 1px', ...styles.noTapHighlight }} + activeStyle={{ + backgroundColor: 'transparent', + }} + hoveredStyle={{ + backgroundColor: 'transparent', + }} + onClick={() => onToggleCollapse?.(group.id)} + > + {collapsed ? ( + <SvgCheveronRight width={14} height={14} /> + ) : ( + <SvgCheveronDown width={14} height={14} /> + )} + </Button> <Text style={{ ...styles.smallText, @@ -582,12 +615,13 @@ const ExpenseGroupTotals = memo(function ExpenseGroupTotals({ // </Droppable> }); -const IncomeGroupTotals = memo(function IncomeGroupTotals({ +const IncomeGroupHeader = memo(function IncomeGroupHeader({ group, budgeted, balance, - style, onEdit, + collapsed, + onToggleCollapse, }) { const listItemRef = useRef(); @@ -596,10 +630,9 @@ const IncomeGroupTotals = memo(function IncomeGroupTotals({ style={{ flexDirection: 'row', alignItems: 'center', - padding: 10, backgroundColor: theme.tableRowHeaderBackground, opacity: !!group.hidden ? 0.5 : undefined, - ...style, + paddingLeft: 0, }} innerRef={listItemRef} > @@ -607,11 +640,28 @@ const IncomeGroupTotals = memo(function IncomeGroupTotals({ role="button" style={{ flex: 1, - justifyContent: 'center', - alignItems: 'flex-start', + alignItems: 'center', + flexDirection: 'row', height: ROW_HEIGHT, }} > + <Button + type="bare" + style={{ margin: '0 1px', ...styles.noTapHighlight }} + activeStyle={{ + backgroundColor: 'transparent', + }} + hoveredStyle={{ + backgroundColor: 'transparent', + }} + onClick={() => onToggleCollapse?.(group.id)} + > + {collapsed ? ( + <SvgCheveronRight width={14} height={14} /> + ) : ( + <SvgCheveronDown width={14} height={14} /> + )} + </Button> <Text style={{ ...styles.smallText, @@ -684,7 +734,6 @@ const IncomeCategory = memo(function IncomeCategory({ style={{ flexDirection: 'row', alignItems: 'center', - padding: 10, backgroundColor: 'transparent', borderBottomWidth: 0, borderTopWidth: index > 0 ? 1 : 0, @@ -799,6 +848,8 @@ const ExpenseGroup = memo(function ExpenseGroup({ showBudgetedCol, show3Cols, showHiddenCategories, + collapsed, + onToggleCollapse, }) { function editable(content) { if (!editMode) { @@ -832,11 +883,11 @@ const ExpenseGroup = memo(function ExpenseGroup({ return editable( <Card style={{ - marginTop: 7, - marginBottom: 7, + marginTop: 4, + marginBottom: 4, }} > - <ExpenseGroupTotals + <ExpenseGroupHeader group={group} showBudgetedCol={showBudgetedCol} budgeted={ @@ -858,11 +909,15 @@ const ExpenseGroup = memo(function ExpenseGroup({ editMode={editMode} onAddCategory={onAddCategory} onEdit={onEditGroup} + collapsed={collapsed} + onToggleCollapse={onToggleCollapse} // onReorderCategory={onReorderCategory} /> {group.categories - .filter(category => !category.hidden || showHiddenCategories) + .filter( + category => !collapsed && (!category.hidden || showHiddenCategories), + ) .map((category, index) => { return ( <ExpenseCategory @@ -924,6 +979,8 @@ function IncomeGroup({ onEditGroup, onEditCategory, onBudgetAction, + collapsed, + onToggleCollapse, }) { return ( <View> @@ -942,7 +999,7 @@ function IncomeGroup({ </View> <Card style={{ marginTop: 0 }}> - <IncomeGroupTotals + <IncomeGroupHeader group={group} budgeted={ type === 'report' ? reportBudget.groupBudgeted(group.id) : null @@ -952,16 +1009,18 @@ function IncomeGroup({ ? reportBudget.groupSumAmount(group.id) : rolloverBudget.groupSumAmount(group.id) } - style={{ - backgroundColor: theme.tableRowHeaderBackground, - }} onAddCategory={onAddCategory} editMode={editMode} onEdit={onEditGroup} + collapsed={collapsed} + onToggleCollapse={onToggleCollapse} /> {group.categories - .filter(category => !category.hidden || showHiddenCategories) + .filter( + category => + !collapsed && (!category.hidden || showHiddenCategories), + ) .map((category, index) => { return ( <IncomeCategory @@ -1020,6 +1079,16 @@ function BudgetGroups({ }); const { incomeGroup, expenseGroups } = separateGroups(categoryGroups); + const [collapsedGroupIds = [], setCollapsedGroupIdsPref] = + useLocalPref('budget.collapsed'); + + const onToggleCollapse = id => { + setCollapsedGroupIdsPref( + collapsedGroupIds.includes(id) + ? collapsedGroupIds.filter(collapsedId => collapsedId !== id) + : [...collapsedGroupIds, id], + ); + }; return ( <View @@ -1048,6 +1117,8 @@ function BudgetGroups({ onBudgetAction={onBudgetAction} show3Cols={show3Cols} showHiddenCategories={showHiddenCategories} + collapsed={collapsedGroupIds.includes(group.id)} + onToggleCollapse={onToggleCollapse} /> ); })} @@ -1065,6 +1136,8 @@ function BudgetGroups({ onEditGroup={onEditGroup} onEditCategory={onEditCategory} onBudgetAction={onBudgetAction} + collapsed={collapsedGroupIds.includes(incomeGroup.id)} + onToggleCollapse={onToggleCollapse} /> )} </View> diff --git a/upcoming-release-notes/2611.md b/upcoming-release-notes/2611.md new file mode 100644 index 0000000000000000000000000000000000000000..979408f50c8effffbe1b45e2e0986f622e4901b9 --- /dev/null +++ b/upcoming-release-notes/2611.md @@ -0,0 +1,6 @@ +--- +category: Features +authors: [joel-jeremy] +--- + +Collapsible budget groups in mobile.