From a521e7eea1715f57c3bbd023847598590f89a184 Mon Sep 17 00:00:00 2001 From: Joel Jeremy Marquez Date: Wed, 27 Mar 2024 07:54:00 -0700 Subject: [PATCH] Extract menus to separate files (change split from #2472) --- .../src/components/budget/index.tsx | 6 +- .../components/budget/report/BalanceMenu.tsx | 44 +++++ .../budget/report/BalanceTooltip.tsx | 24 +-- .../budget/rollover/BalanceMenu.tsx | 67 +++++++ .../budget/rollover/BalanceTooltip.tsx | 57 ++---- .../budget/rollover/CoverTooltip.tsx | 49 +++-- .../budget/rollover/RolloverContext.tsx | 6 +- .../budget/rollover/TransferTooltip.tsx | 108 +++++----- .../rollover/budgetsummary/ToBudget.tsx | 162 +++++---------- .../rollover/budgetsummary/ToBudgetAmount.tsx | 92 +++++++++ .../rollover/budgetsummary/ToBudgetMenu.tsx | 53 +++++ .../src/components/common/Menu.tsx | 53 ++--- .../src/components/mobile/MobileForms.jsx | 150 -------------- .../src/components/mobile/MobileForms.tsx | 186 ++++++++++++++++++ 14 files changed, 625 insertions(+), 432 deletions(-) create mode 100644 packages/desktop-client/src/components/budget/report/BalanceMenu.tsx create mode 100644 packages/desktop-client/src/components/budget/rollover/BalanceMenu.tsx create mode 100644 packages/desktop-client/src/components/budget/rollover/budgetsummary/ToBudgetAmount.tsx create mode 100644 packages/desktop-client/src/components/budget/rollover/budgetsummary/ToBudgetMenu.tsx delete mode 100644 packages/desktop-client/src/components/mobile/MobileForms.jsx create mode 100644 packages/desktop-client/src/components/mobile/MobileForms.tsx diff --git a/packages/desktop-client/src/components/budget/index.tsx b/packages/desktop-client/src/components/budget/index.tsx index 2d339b755ad..cad97746ba6 100644 --- a/packages/desktop-client/src/components/budget/index.tsx +++ b/packages/desktop-client/src/components/budget/index.tsx @@ -39,7 +39,7 @@ import { DynamicBudgetTable } from './DynamicBudgetTable'; import * as report from './report/ReportComponents'; import { ReportProvider } from './report/ReportContext'; import * as rollover from './rollover/RolloverComponents'; -import { RolloverContext } from './rollover/RolloverContext'; +import { RolloverProvider } from './rollover/RolloverContext'; import { prewarmAllMonths, prewarmMonth, switchBudgetType } from './util'; type ReportComponents = { @@ -378,7 +378,7 @@ function BudgetInner(props: BudgetInnerProps) { ); } else { table = ( - - + ); } diff --git a/packages/desktop-client/src/components/budget/report/BalanceMenu.tsx b/packages/desktop-client/src/components/budget/report/BalanceMenu.tsx new file mode 100644 index 00000000000..252afa0401c --- /dev/null +++ b/packages/desktop-client/src/components/budget/report/BalanceMenu.tsx @@ -0,0 +1,44 @@ +import React, { type ComponentPropsWithoutRef } from 'react'; + +import { reportBudget } from 'loot-core/src/client/queries'; + +import { Menu } from '../../common/Menu'; +import { useSheetValue } from '../../spreadsheet/useSheetValue'; + +export type BalanceMenuProps = Omit< + ComponentPropsWithoutRef, + 'onMenuSelect' | 'items' +> & { + categoryId: string; + onCarryover: (carryover: boolean) => void; +}; + +export function BalanceMenu({ + categoryId, + onCarryover, + ...props +}: BalanceMenuProps) { + const carryover = useSheetValue(reportBudget.catCarryover(categoryId)); + return ( + { + switch (name) { + case 'carryover': + onCarryover?.(!carryover); + break; + default: + throw new Error(`Unsupported item: ${name}`); + } + }} + items={[ + { + name: 'carryover', + text: carryover + ? 'Remove overspending rollover' + : 'Rollover overspending', + }, + ]} + /> + ); +} diff --git a/packages/desktop-client/src/components/budget/report/BalanceTooltip.tsx b/packages/desktop-client/src/components/budget/report/BalanceTooltip.tsx index bf806d12b90..dd518f66372 100644 --- a/packages/desktop-client/src/components/budget/report/BalanceTooltip.tsx +++ b/packages/desktop-client/src/components/budget/report/BalanceTooltip.tsx @@ -1,11 +1,9 @@ import React from 'react'; -import { reportBudget } from 'loot-core/src/client/queries'; - -import { Menu } from '../../common/Menu'; -import { useSheetValue } from '../../spreadsheet/useSheetValue'; import { Tooltip } from '../../tooltips'; +import { BalanceMenu } from './BalanceMenu'; + type BalanceTooltipProps = { categoryId: string; tooltip: { close: () => void }; @@ -22,8 +20,6 @@ export function BalanceTooltip({ onClose, ...tooltipProps }: BalanceTooltipProps) { - const carryover = useSheetValue(reportBudget.catCarryover(categoryId)); - const _onClose = () => { tooltip.close(); onClose?.(); @@ -37,22 +33,14 @@ export function BalanceTooltip({ onClose={_onClose} {...tooltipProps} > - { - onBudgetAction(monthIndex, 'carryover', { + { + onBudgetAction?.(monthIndex, 'carryover', { category: categoryId, flag: !carryover, }); - _onClose(); }} - items={[ - { - name: 'carryover', - text: carryover - ? 'Remove overspending rollover' - : 'Rollover overspending', - }, - ]} /> ); diff --git a/packages/desktop-client/src/components/budget/rollover/BalanceMenu.tsx b/packages/desktop-client/src/components/budget/rollover/BalanceMenu.tsx new file mode 100644 index 00000000000..803fcbe58b0 --- /dev/null +++ b/packages/desktop-client/src/components/budget/rollover/BalanceMenu.tsx @@ -0,0 +1,67 @@ +import React, { type ComponentPropsWithoutRef } from 'react'; + +import { rolloverBudget } from 'loot-core/src/client/queries'; + +import { Menu } from '../../common/Menu'; +import { useSheetValue } from '../../spreadsheet/useSheetValue'; + +type BalanceMenuProps = Omit< + ComponentPropsWithoutRef, + 'onMenuSelect' | 'items' +> & { + categoryId: string; + onTransfer: () => void; + onCarryover: (carryOver: boolean) => void; + onCover: () => void; +}; + +export function BalanceMenu({ + categoryId, + onTransfer, + onCarryover, + onCover, + ...props +}: BalanceMenuProps) { + const carryover = useSheetValue(rolloverBudget.catCarryover(categoryId)); + const balance = useSheetValue(rolloverBudget.catBalance(categoryId)); + return ( + { + switch (name) { + case 'transfer': + onTransfer?.(); + break; + case 'carryover': + onCarryover?.(!carryover); + break; + case 'cover': + onCover?.(); + break; + default: + throw new Error(`Unsupported item: ${name}`); + } + }} + items={[ + { + name: 'transfer', + text: 'Transfer to another category', + }, + { + name: 'carryover', + text: carryover + ? 'Remove overspending rollover' + : 'Rollover overspending', + }, + ...(balance < 0 + ? [ + { + name: 'cover', + text: 'Cover overspending', + }, + ] + : []), + ]} + /> + ); +} diff --git a/packages/desktop-client/src/components/budget/rollover/BalanceTooltip.tsx b/packages/desktop-client/src/components/budget/rollover/BalanceTooltip.tsx index 5ad6251205a..66b8f86ba30 100644 --- a/packages/desktop-client/src/components/budget/rollover/BalanceTooltip.tsx +++ b/packages/desktop-client/src/components/budget/rollover/BalanceTooltip.tsx @@ -2,10 +2,10 @@ import React, { useState } from 'react'; import { rolloverBudget } from 'loot-core/src/client/queries'; -import { Menu } from '../../common/Menu'; import { useSheetValue } from '../../spreadsheet/useSheetValue'; import { Tooltip } from '../../tooltips'; +import { BalanceMenu } from './BalanceMenu'; import { CoverTooltip } from './CoverTooltip'; import { TransferTooltip } from './TransferTooltip'; @@ -16,6 +16,7 @@ type BalanceTooltipProps = { onBudgetAction: (idx: number, action: string, arg?: unknown) => void; onClose?: () => void; }; + export function BalanceTooltip({ categoryId, tooltip, @@ -24,8 +25,7 @@ export function BalanceTooltip({ onClose, ...tooltipProps }: BalanceTooltipProps) { - const carryover = useSheetValue(rolloverBudget.catCarryover(categoryId)); - const balance = useSheetValue(rolloverBudget.catBalance(categoryId)); + const catBalance = useSheetValue(rolloverBudget.catBalance(categoryId)); const [menu, setMenu] = useState('menu'); const _onClose = () => { @@ -43,52 +43,31 @@ export function BalanceTooltip({ onClose={_onClose} {...tooltipProps} > - { - if (type === 'carryover') { - onBudgetAction(monthIndex, 'carryover', { - category: categoryId, - flag: !carryover, - }); - _onClose(); - } else { - setMenu(type); - } + { + onBudgetAction(monthIndex, 'carryover', { + category: categoryId, + flag: carryover, + }); + _onClose(); }} - items={[ - { - name: 'transfer', - text: 'Transfer to another category', - }, - { - name: 'carryover', - text: carryover - ? 'Remove overspending rollover' - : 'Rollover overspending', - }, - ...(balance < 0 - ? [ - { - name: 'cover', - text: 'Cover overspending', - }, - ] - : []), - ]} + onTransfer={() => setMenu('transfer')} + onCover={() => setMenu('cover')} /> )} {menu === 'transfer' && ( { + onSubmit={(amount, toCategoryId) => { onBudgetAction(monthIndex, 'transfer-category', { amount, from: categoryId, - to: toCategory, + to: toCategoryId, }); }} /> @@ -97,10 +76,10 @@ export function BalanceTooltip({ {menu === 'cover' && ( { + onSubmit={fromCategoryId => { onBudgetAction(monthIndex, 'cover', { to: categoryId, - from: fromCategory, + from: fromCategoryId, }); }} /> diff --git a/packages/desktop-client/src/components/budget/rollover/CoverTooltip.tsx b/packages/desktop-client/src/components/budget/rollover/CoverTooltip.tsx index 590fe23e773..db8fb09d56b 100644 --- a/packages/desktop-client/src/components/budget/rollover/CoverTooltip.tsx +++ b/packages/desktop-client/src/components/budget/rollover/CoverTooltip.tsx @@ -10,7 +10,7 @@ import { addToBeBudgetedGroup } from '../util'; type CoverTooltipProps = { tooltipProps?: ComponentProps; - onSubmit: (category: unknown) => void; + onSubmit: (categoryId: string) => void; onClose: () => void; }; export function CoverTooltip({ @@ -18,18 +18,10 @@ export function CoverTooltip({ onSubmit, onClose, }: CoverTooltipProps) { - const { grouped } = useCategories(); - const categoryGroups = addToBeBudgetedGroup( - grouped.filter(g => !g.is_income), - ); - const [category, setCategory] = useState(null); - - function submit() { - if (category) { - onSubmit(category); - onClose(); - } - } + const _onSubmit = (categoryId: string) => { + onSubmit?.(categoryId); + onClose?.(); + }; return ( + + + ); +} + +type CoverProps = { + onSubmit: (categoryId: string) => void; +}; + +function Cover({ onSubmit }: CoverProps) { + let { grouped: categoryGroups } = useCategories(); + categoryGroups = addToBeBudgetedGroup( + categoryGroups.filter(g => !g.is_income), + ); + const [categoryId, setCategoryId] = useState(null); + + function submit() { + if (categoryId) { + onSubmit(categoryId); + } + } + return ( + <> Cover from category: {node => ( g.id === categoryId)} openOnFocus={true} - onUpdate={() => {}} - onSelect={(id: string | undefined) => setCategory(id || null)} + onSelect={(id: string | undefined) => setCategoryId(id || null)} inputProps={{ inputRef: node, onKeyDown: e => { @@ -56,6 +70,7 @@ export function CoverTooltip({ submit(); } }, + placeholder: '(none)', }} showHiddenCategories={false} /> @@ -79,6 +94,6 @@ export function CoverTooltip({ Transfer - + ); } diff --git a/packages/desktop-client/src/components/budget/rollover/RolloverContext.tsx b/packages/desktop-client/src/components/budget/rollover/RolloverContext.tsx index 583444a4716..ea05cd87374 100644 --- a/packages/desktop-client/src/components/budget/rollover/RolloverContext.tsx +++ b/packages/desktop-client/src/components/budget/rollover/RolloverContext.tsx @@ -22,15 +22,15 @@ const Context = createContext({ currentMonth: 'unknown', }); -type RolloverContextProps = Omit & { +type RolloverProviderProps = Omit & { children: ReactNode; }; -export function RolloverContext({ +export function RolloverProvider({ summaryCollapsed, onBudgetAction, onToggleSummaryCollapse, children, -}: RolloverContextProps) { +}: RolloverProviderProps) { const currentMonth = monthUtils.currentMonth(); return ( diff --git a/packages/desktop-client/src/components/budget/rollover/TransferTooltip.tsx b/packages/desktop-client/src/components/budget/rollover/TransferTooltip.tsx index d321eea3aa3..37e083588ea 100644 --- a/packages/desktop-client/src/components/budget/rollover/TransferTooltip.tsx +++ b/packages/desktop-client/src/components/budget/rollover/TransferTooltip.tsx @@ -1,11 +1,6 @@ -import React, { - useState, - useContext, - useEffect, - type ComponentPropsWithoutRef, -} from 'react'; +import type React from 'react'; +import { useState, type ComponentPropsWithoutRef } from 'react'; -import { useSpreadsheet } from 'loot-core/src/client/SpreadsheetProvider'; import { evalArithmetic } from 'loot-core/src/shared/arithmetic'; import { integerToCurrency, amountToInteger } from 'loot-core/src/shared/util'; @@ -15,79 +10,78 @@ import { Button } from '../../common/Button'; import { InitialFocus } from '../../common/InitialFocus'; import { Input } from '../../common/Input'; import { View } from '../../common/View'; -import { NamespaceContext } from '../../spreadsheet/NamespaceContext'; import { Tooltip } from '../../tooltips'; import { addToBeBudgetedGroup } from '../util'; type TransferTooltipProps = ComponentPropsWithoutRef & { initialAmount?: number; - initialAmountName?: string; showToBeBudgeted?: boolean; - onSubmit: (amount: number, category: string) => void; + onSubmit: (amount: number, categoryId: string) => void; }; + export function TransferTooltip({ initialAmount = 0, - initialAmountName, showToBeBudgeted, onSubmit, onClose, position = 'bottom-right', ...props }: TransferTooltipProps) { - const spreadsheet = useSpreadsheet(); - const sheetName = useContext(NamespaceContext); - let { grouped: categoryGroups } = useCategories(); + const _onSubmit = (amount: number, categoryId: string) => { + onSubmit?.(amount, categoryId); + onClose?.(); + }; + + return ( + + + + ); +} + +type TransferProps = { + amount: number; + showToBeBudgeted: boolean; + onSubmit: (amount: number, categoryId: string) => void; +}; +function Transfer({ + amount: initialAmount, + showToBeBudgeted, + onSubmit, +}: TransferProps) { + let { grouped: categoryGroups } = useCategories(); categoryGroups = categoryGroups.filter(g => !g.is_income); if (showToBeBudgeted) { categoryGroups = addToBeBudgetedGroup(categoryGroups); } + const _initialAmount = integerToCurrency(Math.max(initialAmount, 0)); const [amount, setAmount] = useState(null); - const [category, setCategory] = useState(null); - - useEffect(() => { - (async () => { - if (initialAmountName) { - const node = await spreadsheet.get(sheetName, initialAmountName); - setAmount(integerToCurrency(Math.max(node.value as number, 0))); - } else { - setAmount(integerToCurrency(Math.max(initialAmount, 0))); - } - })(); - }, []); + const [categoryId, setCategoryId] = useState(null); - function submit(newAmount: string) { - const parsedAmount = evalArithmetic(newAmount); - if (parsedAmount && category) { - onSubmit(amountToInteger(parsedAmount), category); - onClose(); + const _onSubmit = (newAmount: string | null, categoryId: string | null) => { + const parsedAmount = evalArithmetic(newAmount || ''); + if (parsedAmount && categoryId) { + onSubmit?.(amountToInteger(parsedAmount), categoryId); } - } - - if (amount === null) { - // Don't render anything until we have the amount to show. This - // ensures that the amount field is focused and fully selected - // when it's initially rendered (instead of being updated - // afterwards and losing selection) - return null; - } + }; return ( - + <> Transfer this amount: setAmount(e.target['value'])} - onEnter={() => submit(amount)} + defaultValue={_initialAmount} + onUpdate={value => setAmount(value)} + onEnter={() => _onSubmit(amount, categoryId)} /> @@ -95,11 +89,13 @@ export function TransferTooltip({ g.id === categoryId)} openOnFocus={true} - onUpdate={() => {}} - onSelect={(id: string | undefined) => setCategory(id || null)} - inputProps={{ onEnter: () => submit(amount), placeholder: '(none)' }} + onSelect={(id: string | undefined) => setCategoryId(id || null)} + inputProps={{ + onEnter: () => _onSubmit(amount, categoryId), + placeholder: '(none)', + }} showHiddenCategories={true} /> @@ -116,11 +112,11 @@ export function TransferTooltip({ paddingTop: 3, paddingBottom: 3, }} - onClick={() => submit(amount)} + onClick={() => _onSubmit(amount, categoryId)} > Transfer - + ); } diff --git a/packages/desktop-client/src/components/budget/rollover/budgetsummary/ToBudget.tsx b/packages/desktop-client/src/components/budget/rollover/budgetsummary/ToBudget.tsx index 02b1d1da6a4..75051811aa0 100644 --- a/packages/desktop-client/src/components/budget/rollover/budgetsummary/ToBudget.tsx +++ b/packages/desktop-client/src/components/budget/rollover/budgetsummary/ToBudget.tsx @@ -1,23 +1,15 @@ import React, { useState, type ComponentPropsWithoutRef } from 'react'; -import { css } from 'glamor'; - import { rolloverBudget } from 'loot-core/src/client/queries'; -import { theme, styles, type CSSProperties } from '../../../../style'; -import { Block } from '../../../common/Block'; -import { HoverTarget } from '../../../common/HoverTarget'; -import { Menu } from '../../../common/Menu'; -import { View } from '../../../common/View'; -import { PrivacyFilter } from '../../../PrivacyFilter'; -import { useFormat } from '../../../spreadsheet/useFormat'; -import { useSheetName } from '../../../spreadsheet/useSheetName'; +import { type CSSProperties } from '../../../../style'; import { useSheetValue } from '../../../spreadsheet/useSheetValue'; import { Tooltip } from '../../../tooltips'; import { HoldTooltip } from '../HoldTooltip'; import { TransferTooltip } from '../TransferTooltip'; -import { TotalsList } from './TotalsList'; +import { ToBudgetAmount } from './ToBudgetAmount'; +import { ToBudgetMenu } from './ToBudgetMenu'; type ToBudgetProps = { month: string; @@ -44,116 +36,62 @@ export function ToBudget({ transferTooltipProps, }: ToBudgetProps) { const [menuOpen, setMenuOpen] = useState(null); - const sheetName = useSheetName(rolloverBudget.toBudget); const sheetValue = useSheetValue({ name: rolloverBudget.toBudget, value: 0, }); - const format = useFormat(); const availableValue = parseInt(sheetValue); - const num = isNaN(availableValue) ? 0 : availableValue; - const isNegative = num < 0; return ( - - {isNegative ? 'Overbudgeted:' : 'To Budget:'} - - ( - - - - )} + <> + setMenuOpen('actions')} + prevMonthName={prevMonthName} + showTotalsTooltipOnHover={showTotalsTooltipOnHover} + totalsTooltipProps={totalsTooltipProps} + style={style} + amountStyle={amountStyle} + /> + {menuOpen === 'actions' && ( + setMenuOpen(null)} + {...menuTooltipProps} > - - setMenuOpen('actions')} - data-cellname={sheetName} - className={`${css([ - styles.veryLargeText, - { - fontWeight: 400, - userSelect: 'none', - cursor: 'pointer', - color: isNegative ? theme.errorText : theme.pageTextPositive, - marginBottom: -1, - borderBottom: '1px solid transparent', - ':hover': { - borderColor: isNegative - ? theme.errorBorder - : theme.pageTextPositive, - }, - }, - amountStyle, - ])}`} - > - {format(num, 'financial')} - - - - {menuOpen === 'actions' && ( - setMenuOpen(null)} - {...menuTooltipProps} - > - { - if (type === 'reset-buffer') { - onBudgetAction(month, 'reset-hold'); - setMenuOpen(null); - } else { - setMenuOpen(type); - } - }} - items={[ - { - name: 'transfer', - text: 'Move to a category', - }, - { - name: 'buffer', - text: 'Hold for next month', - }, - { - name: 'reset-buffer', - text: 'Reset next month’s buffer', - }, - ]} - /> - - )} - {menuOpen === 'buffer' && ( - setMenuOpen(null)} - onSubmit={amount => { - onBudgetAction(month, 'hold', { amount }); - }} - {...holdTooltipProps} - /> - )} - {menuOpen === 'transfer' && ( - setMenuOpen(null)} - onSubmit={(amount, category) => { - onBudgetAction(month, 'transfer-available', { - amount, - category, - }); + setMenuOpen('transfer')} + onHoldBuffer={() => setMenuOpen('buffer')} + onResetHoldBuffer={() => { + onBudgetAction(month, 'reset-hold'); + setMenuOpen(null); }} - {...transferTooltipProps} /> - )} - - + + )} + {menuOpen === 'buffer' && ( + setMenuOpen(null)} + onSubmit={amount => { + onBudgetAction(month, 'hold', { amount }); + }} + {...holdTooltipProps} + /> + )} + {menuOpen === 'transfer' && ( + setMenuOpen(null)} + onSubmit={(amount, category) => { + onBudgetAction(month, 'transfer-available', { + amount, + category, + }); + }} + {...transferTooltipProps} + /> + )} + ); } diff --git a/packages/desktop-client/src/components/budget/rollover/budgetsummary/ToBudgetAmount.tsx b/packages/desktop-client/src/components/budget/rollover/budgetsummary/ToBudgetAmount.tsx new file mode 100644 index 00000000000..4764903c74b --- /dev/null +++ b/packages/desktop-client/src/components/budget/rollover/budgetsummary/ToBudgetAmount.tsx @@ -0,0 +1,92 @@ +import React, { type ComponentPropsWithoutRef } from 'react'; + +import { css } from 'glamor'; + +import { rolloverBudget } from 'loot-core/src/client/queries'; + +import { theme, styles, type CSSProperties } from '../../../../style'; +import { Block } from '../../../common/Block'; +import { HoverTarget } from '../../../common/HoverTarget'; +import { View } from '../../../common/View'; +import { PrivacyFilter } from '../../../PrivacyFilter'; +import { useFormat } from '../../../spreadsheet/useFormat'; +import { useSheetName } from '../../../spreadsheet/useSheetName'; +import { useSheetValue } from '../../../spreadsheet/useSheetValue'; +import { Tooltip } from '../../../tooltips'; + +import { TotalsList } from './TotalsList'; + +type ToBudgetAmountProps = { + prevMonthName: string; + showTotalsTooltipOnHover?: boolean; + totalsTooltipProps?: ComponentPropsWithoutRef; + style?: CSSProperties; + amountStyle?: CSSProperties; + onClick: () => void; +}; + +export function ToBudgetAmount({ + prevMonthName, + showTotalsTooltipOnHover, + totalsTooltipProps, + style, + amountStyle, + onClick, +}: ToBudgetAmountProps) { + const sheetName = useSheetName(rolloverBudget.toBudget); + const sheetValue = useSheetValue({ + name: rolloverBudget.toBudget, + value: 0, + }); + const format = useFormat(); + const availableValue = parseInt(sheetValue); + const num = isNaN(availableValue) ? 0 : availableValue; + const isNegative = num < 0; + + return ( + + {isNegative ? 'Overbudgeted:' : 'To Budget:'} + + ( + + + + )} + > + + + {format(num, 'financial')} + + + + + + ); +} diff --git a/packages/desktop-client/src/components/budget/rollover/budgetsummary/ToBudgetMenu.tsx b/packages/desktop-client/src/components/budget/rollover/budgetsummary/ToBudgetMenu.tsx new file mode 100644 index 00000000000..183e7d28753 --- /dev/null +++ b/packages/desktop-client/src/components/budget/rollover/budgetsummary/ToBudgetMenu.tsx @@ -0,0 +1,53 @@ +import React, { type ComponentPropsWithoutRef } from 'react'; + +import { Menu } from '../../../common/Menu'; + +type ToBudgetMenuProps = Omit< + ComponentPropsWithoutRef, + 'onMenuSelect' | 'items' +> & { + onTransfer: () => void; + onHoldBuffer: () => void; + onResetHoldBuffer: () => void; +}; +export function ToBudgetMenu({ + onTransfer, + onHoldBuffer, + onResetHoldBuffer, + ...props +}: ToBudgetMenuProps) { + return ( + { + switch (name) { + case 'transfer': + onTransfer?.(); + break; + case 'buffer': + onHoldBuffer?.(); + break; + case 'reset-buffer': + onResetHoldBuffer?.(); + break; + default: + throw new Error(`Unsupported item: ${name}`); + } + }} + items={[ + { + name: 'transfer', + text: 'Move to a category', + }, + { + name: 'buffer', + text: 'Hold for next month', + }, + { + name: 'reset-buffer', + text: 'Reset next month’s buffer', + }, + ]} + /> + ); +} diff --git a/packages/desktop-client/src/components/common/Menu.tsx b/packages/desktop-client/src/components/common/Menu.tsx index 4d5ed8691dc..a948ca5ec4e 100644 --- a/packages/desktop-client/src/components/common/Menu.tsx +++ b/packages/desktop-client/src/components/common/Menu.tsx @@ -1,11 +1,11 @@ import { - type FunctionComponent, type ReactElement, type ReactNode, - createElement, useEffect, useRef, useState, + type ComponentType, + type SVGProps, } from 'react'; import { type CSSProperties, theme } from '../../style'; @@ -34,16 +34,10 @@ type MenuItem = { type?: string | symbol; name: string; disabled?: boolean; - // eslint-disable-next-line @typescript-eslint/ban-types - icon?: FunctionComponent<{ - width: number; - height: number; - style: CSSProperties; - }>; + icon?: ComponentType>; iconSize?: number; text: string; key?: string; - style?: CSSProperties; toggle?: boolean; tooltip?: string; }; @@ -54,6 +48,7 @@ type MenuProps = { items: Array; onMenuSelect?: (itemName: T['name']) => void; style?: CSSProperties; + getItemStyle?: (item: T) => CSSProperties; }; export function Menu({ @@ -62,6 +57,7 @@ export function Menu({ items: allItems, onMenuSelect, style, + getItemStyle, }: MenuProps) { const elRef = useRef(null); const items = allItems.filter(x => x); @@ -149,6 +145,7 @@ export function Menu({ } const lastItem = items[idx - 1]; + const Icon = item.icon; return ( ({ backgroundColor: theme.menuItemBackgroundHover, color: theme.menuItemTextHover, }), - ...item.style, + ...getItemStyle?.(item), }} - onMouseEnter={() => setHoveredIndex(idx)} - onMouseLeave={() => setHoveredIndex(null)} - onClick={() => - !item.disabled && - onMenuSelect && - item.toggle === undefined && - onMenuSelect(item.name) - } + onPointerEnter={() => setHoveredIndex(idx)} + onPointerLeave={() => setHoveredIndex(null)} + onClick={() => !item.disabled && onMenuSelect?.(item.name)} > {/* Force it to line up evenly */} {item.toggle === undefined ? ( <> - - {item.icon && - createElement(item.icon, { - width: item.iconSize || 10, - height: item.iconSize || 10, - style: { - marginRight: 7, - width: item.iconSize || 10, - }, - })} - + {Icon && ( + + )} {item.text} @@ -210,12 +198,9 @@ export function Menu({ id={item.name} checked={item.toggle} onColor={theme.pageTextPositive} - style={{ marginLeft: 5, ...item.style }} + style={{ marginLeft: 5 }} onToggle={() => - !item.disabled && - onMenuSelect && - item.toggle !== undefined && - onMenuSelect(item.name) + !item.disabled && item.toggle && onMenuSelect?.(item.name) } /> diff --git a/packages/desktop-client/src/components/mobile/MobileForms.jsx b/packages/desktop-client/src/components/mobile/MobileForms.jsx deleted file mode 100644 index e3f46e0b1d4..00000000000 --- a/packages/desktop-client/src/components/mobile/MobileForms.jsx +++ /dev/null @@ -1,150 +0,0 @@ -import { forwardRef } from 'react'; - -import { css } from 'glamor'; - -import { theme, styles } from '../../style'; -import { Button } from '../common/Button'; -import { Input } from '../common/Input'; -import { Text } from '../common/Text'; -import { View } from '../common/View'; - -const FIELD_HEIGHT = 40; - -export function FieldLabel({ title, flush, style }) { - return ( - - {title} - - ); -} - -const valueStyle = { - borderWidth: 1, - borderColor: theme.formInputBorder, - marginLeft: 8, - marginRight: 8, - height: FIELD_HEIGHT, -}; - -export const InputField = forwardRef(function InputField( - { disabled, style, onUpdate, ...props }, - ref, -) { - return ( - - ); -}); - -export function TapField({ - value, - children, - disabled, - rightContent, - style, - textStyle, - onClick, - ...props -}) { - return ( - - ); -} - -export function BooleanField({ checked, onUpdate, style, disabled = false }) { - return ( - onUpdate(e.target.checked)} - className={`${css([ - { - marginInline: styles.mobileEditingPadding, - flexShrink: 0, - appearance: 'none', - outline: 0, - border: '1px solid ' + theme.formInputBorder, - borderRadius: 4, - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - color: theme.checkboxText, - backgroundColor: theme.tableBackground, - ':checked': { - border: '1px solid ' + theme.checkboxBorderSelected, - backgroundColor: theme.checkboxBackgroundSelected, - '::after': { - display: 'block', - background: - theme.checkboxBackgroundSelected + - // eslint-disable-next-line rulesdir/typography - ' url(\'data:image/svg+xml; utf8,\') 15px 15px', - width: 15, - height: 15, - content: ' ', - }, - }, - }, - style, - ])}`} - /> - ); -} diff --git a/packages/desktop-client/src/components/mobile/MobileForms.tsx b/packages/desktop-client/src/components/mobile/MobileForms.tsx new file mode 100644 index 00000000000..6141192bdab --- /dev/null +++ b/packages/desktop-client/src/components/mobile/MobileForms.tsx @@ -0,0 +1,186 @@ +import React, { + type ComponentPropsWithoutRef, + forwardRef, + type ReactNode, +} from 'react'; + +import { css } from 'glamor'; + +import { theme, styles, type CSSProperties } from '../../style'; +import { Button } from '../common/Button'; +import { Input } from '../common/Input'; +import { Text } from '../common/Text'; +import { View } from '../common/View'; + +type FieldLabelProps = { + title: string; + flush?: boolean; + style?: CSSProperties; +}; + +export function FieldLabel({ title, flush, style }: FieldLabelProps) { + return ( + + {title} + + ); +} + +const valueStyle = { + borderWidth: 1, + borderColor: theme.formInputBorder, + marginLeft: 8, + marginRight: 8, + height: styles.mobileMinHeight, +}; + +type InputFieldProps = ComponentPropsWithoutRef; + +export const InputField = forwardRef( + ({ disabled, style, onUpdate, ...props }, ref) => { + return ( + + ); + }, +); + +InputField.displayName = 'InputField'; + +type TapFieldProps = ComponentPropsWithoutRef & { + rightContent?: ReactNode; +}; + +export const TapField = forwardRef( + ( + { + value, + children, + disabled, + rightContent, + style, + textStyle, + onClick, + ...props + }, + ref, + ) => { + return ( + + ); + }, +); + +TapField.displayName = 'TapField'; + +type BooleanFieldProps = { + checked: boolean; + disabled?: boolean; + onUpdate?: (checked: boolean) => void; + style?: CSSProperties; +}; + +export function BooleanField({ + checked, + onUpdate, + style, + disabled = false, +}: BooleanFieldProps) { + return ( + onUpdate?.(e.target.checked)} + className={`${css([ + { + marginInline: styles.mobileEditingPadding, + flexShrink: 0, + appearance: 'none', + outline: 0, + border: '1px solid ' + theme.formInputBorder, + borderRadius: 4, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + color: theme.checkboxText, + backgroundColor: theme.tableBackground, + ':checked': { + border: '1px solid ' + theme.checkboxBorderSelected, + backgroundColor: theme.checkboxBackgroundSelected, + '::after': { + display: 'block', + background: + theme.checkboxBackgroundSelected + + // eslint-disable-next-line rulesdir/typography + ' url(\'data:image/svg+xml; utf8,\') 15px 15px', + width: 15, + height: 15, + content: ' ', + }, + }, + }, + style, + ])}`} + /> + ); +}