diff --git a/packages/desktop-client/e2e/mobile.test.js b/packages/desktop-client/e2e/mobile.test.js index 0b1118b68102345698e4c714806f2ab7cca7c614..665b17e3f31f28fa53585e802e398579038adbe4 100644 --- a/packages/desktop-client/e2e/mobile.test.js +++ b/packages/desktop-client/e2e/mobile.test.js @@ -42,6 +42,9 @@ test.describe('Mobile', () => { 'Mortgage', 'Water', 'Power', + 'Starting Balances', + 'Misc', + 'Income', ]); await expect(page).toMatchThemeScreenshots(); }); 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 8398ad9a138e875442bb62625432c154d78d7eb2..c805ccaa2b3ee46dc51d0e9da1a8a8c854594240 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 bcbf6317cb7b2d82de9b5db6833953e2f4f3df4d..7cb998dbf9ff26273c64f9ae284818a83f61900a 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/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-3-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-3-chromium-linux.png index a8e5f9cadea1c04b4c4686e2ac1fe1797b995c4b..f3184eb89dbb04c3f9c1cb7676bd498f97b7842f 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-3-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-3-chromium-linux.png differ diff --git a/packages/desktop-client/package.json b/packages/desktop-client/package.json index 9757f89b2b4fc99bba39d8a7240db0f7995db7bb..0a59dccfc26949a9199d49ba7533a098753f9625 100644 --- a/packages/desktop-client/package.json +++ b/packages/desktop-client/package.json @@ -32,6 +32,7 @@ "@use-gesture/react": "^10.3.0", "@vitejs/plugin-basic-ssl": "^1.1.0", "@vitejs/plugin-react-swc": "^3.6.0", + "auto-text-size": "^0.2.3", "chokidar": "^3.5.3", "cross-env": "^7.0.3", "date-fns": "^2.30.0", diff --git a/packages/desktop-client/src/components/budget/BalanceWithCarryover.tsx b/packages/desktop-client/src/components/budget/BalanceWithCarryover.tsx index 0dc58ae175056f2f0c61d4e26f4eedc8d6ffecbc..87a3f07697136f636fa10ac622ab660ee959f5ce 100644 --- a/packages/desktop-client/src/components/budget/BalanceWithCarryover.tsx +++ b/packages/desktop-client/src/components/budget/BalanceWithCarryover.tsx @@ -1,23 +1,26 @@ // @ts-strict-ignore -import React, { type ComponentProps } from 'react'; +import React, { type ComponentPropsWithoutRef } from 'react'; import { useFeatureFlag } from '../../hooks/useFeatureFlag'; import { SvgArrowThinRight } from '../../icons/v1'; import { useResponsive } from '../../ResponsiveProvider'; import { type CSSProperties } from '../../style'; import { View } from '../common/View'; +import { type Binding } from '../spreadsheet'; import { CellValue } from '../spreadsheet/CellValue'; import { useSheetValue } from '../spreadsheet/useSheetValue'; import { makeBalanceAmountStyle } from './util'; -type BalanceWithCarryoverProps = { - carryover: ComponentProps<typeof CellValue>['binding']; - balance: ComponentProps<typeof CellValue>['binding']; - goal?: ComponentProps<typeof CellValue>['binding']; - budgeted?: ComponentProps<typeof CellValue>['binding']; +type BalanceWithCarryoverProps = Omit< + ComponentPropsWithoutRef<typeof CellValue>, + 'binding' +> & { + carryover: Binding; + balance: Binding; + goal?: Binding; + budgeted?: Binding; disabled?: boolean; - balanceStyle?: CSSProperties; carryoverStyle?: CSSProperties; }; export function BalanceWithCarryover({ @@ -26,8 +29,8 @@ export function BalanceWithCarryover({ goal, budgeted, disabled, - balanceStyle, carryoverStyle, + ...props }: BalanceWithCarryoverProps) { const carryoverValue = useSheetValue(carryover); const balanceValue = useSheetValue(balance); @@ -40,6 +43,7 @@ export function BalanceWithCarryover({ return ( <> <CellValue + {...props} binding={balance} type="financial" getStyle={value => @@ -53,9 +57,8 @@ export function BalanceWithCarryover({ textAlign: 'right', ...(!disabled && { cursor: 'pointer', - ':hover': { textDecoration: 'underline' }, }), - ...balanceStyle, + ...props.style, }} /> {carryoverValue && ( diff --git a/packages/desktop-client/src/components/budget/report/ReportComponents.tsx b/packages/desktop-client/src/components/budget/report/ReportComponents.tsx index 5d09472c8ffc755b9d8aaa5312eb25293cae213c..8b05023d2b86d3e1a02741bd1f0865b9f5a766a8 100644 --- a/packages/desktop-client/src/components/budget/report/ReportComponents.tsx +++ b/packages/desktop-client/src/components/budget/report/ReportComponents.tsx @@ -330,6 +330,9 @@ export const CategoryMonth = memo(function CategoryMonth({ balance={reportBudget.catBalance(category.id)} goal={reportBudget.catGoal(category.id)} budgeted={reportBudget.catBudgeted(category.id)} + style={{ + ':hover': { textDecoration: 'underline' }, + }} /> </span> {balanceTooltip.isOpen && ( diff --git a/packages/desktop-client/src/components/budget/rollover/RolloverComponents.tsx b/packages/desktop-client/src/components/budget/rollover/RolloverComponents.tsx index 492fb5b1c5a0880aa31a3703a1edb791174fde11..f24cea5afdc7c43fe650a38ff71e3a6d1ec42ffd 100644 --- a/packages/desktop-client/src/components/budget/rollover/RolloverComponents.tsx +++ b/packages/desktop-client/src/components/budget/rollover/RolloverComponents.tsx @@ -323,6 +323,9 @@ export const ExpenseCategoryMonth = memo(function ExpenseCategoryMonth({ balance={rolloverBudget.catBalance(category.id)} goal={rolloverBudget.catGoal(category.id)} budgeted={rolloverBudget.catBudgeted(category.id)} + style={{ + ':hover': { textDecoration: 'underline' }, + }} /> </span> diff --git a/packages/desktop-client/src/components/common/Label.tsx b/packages/desktop-client/src/components/common/Label.tsx index eb71d346876a8c6630aa76c5a0d94d05bf393c30..eed751274ef1b7c3cb6fdbb58856a5c146793039 100644 --- a/packages/desktop-client/src/components/common/Label.tsx +++ b/packages/desktop-client/src/components/common/Label.tsx @@ -1,4 +1,4 @@ -import { type ReactNode } from 'react'; +import { forwardRef, type ReactNode } from 'react'; import { type CSSProperties, theme, styles } from '../../style'; @@ -9,19 +9,24 @@ type LabelProps = { style?: CSSProperties; }; -export function Label({ title, style }: LabelProps) { - return ( - <Text - style={{ - ...styles.text, - color: theme.tableRowHeaderText, - textAlign: 'right', - fontSize: 14, - marginBottom: 2, - ...style, - }} - > - {title} - </Text> - ); -} +export const Label = forwardRef<HTMLSpanElement, LabelProps>( + ({ title, style }: LabelProps, ref) => { + return ( + <Text + ref={ref} + style={{ + ...styles.text, + color: theme.tableRowHeaderText, + textAlign: 'right', + fontSize: 14, + marginBottom: 2, + ...style, + }} + > + {title} + </Text> + ); + }, +); + +Label.displayName = 'Label'; diff --git a/packages/desktop-client/src/components/common/Modal.tsx b/packages/desktop-client/src/components/common/Modal.tsx index 64ec91e6d4c7318deb001323119ab7412a9aa4e9..802bac03d159a75534c21d85e2d68e273d5ea114 100644 --- a/packages/desktop-client/src/components/common/Modal.tsx +++ b/packages/desktop-client/src/components/common/Modal.tsx @@ -12,20 +12,18 @@ import React, { import { useHotkeysContext } from 'react-hotkeys-hook'; import ReactModal from 'react-modal'; -import { useHover } from 'usehooks-ts'; +import { AutoTextSize } from 'auto-text-size'; -import { useMergedRefs } from '../../hooks/useMergedRefs'; import { AnimatedLoading } from '../../icons/AnimatedLoading'; import { SvgLogo } from '../../icons/logo'; import { SvgDelete } from '../../icons/v0'; -import { SvgClose } from '../../icons/v1'; import { type CSSProperties, styles, theme } from '../../style'; import { tokens } from '../../tokens'; import { Button } from './Button'; import { Input } from './Input'; import { Text } from './Text'; -import { Tooltip } from './Tooltip'; +import { TextOneLine } from './TextOneLine'; import { View } from './View'; export type ModalProps = { @@ -377,39 +375,8 @@ export function ModalTitle({ shrinkOnOverflow = false, }: ModalTitleProps) { const [isEditing, setIsEditing] = useState(false); - const tooltipTriggerRef = useRef(); - // Dynamic font size to avoid ellipsis. - const textRef = useRef<HTMLSpanElement>(); - const [textOverflowed, setTextOverflowed] = useState(false); - const [textFontSize, setTextFontSize] = useState(25); - - useEffect(() => { - const containerWidth = textRef.current.offsetWidth; - const textWidth = textRef.current.scrollWidth; - - if (textWidth > containerWidth) { - setTextOverflowed(true); - } else { - setTextOverflowed(false); - } - }, [title]); - - useEffect(() => { - if (textOverflowed && shrinkOnOverflow) { - const containerWidth = textRef.current.offsetWidth; - const textWidth = textRef.current.scrollWidth; - const newFontSize = Math.floor( - (containerWidth / textWidth) * textFontSize, - ); - setTextFontSize(newFontSize); - } - }, [title, textFontSize, shrinkOnOverflow, textOverflowed]); - - const mergedTextRef = useMergedRefs(textRef, tooltipTriggerRef); - const isHovered = useHover(tooltipTriggerRef); - - const _onEdit = () => { + const onTitleClick = () => { if (isEditable) { setIsEditing(true); } @@ -453,42 +420,44 @@ export function ModalTitle({ }} /> ) : ( - <Tooltip - content={ - <View + <View + style={{ + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + }} + > + {shrinkOnOverflow ? ( + <AutoTextSize + as={Text} + minFontSizePx={15} + maxFontSizePx={25} + onClick={onTitleClick} style={{ - flexDirection: 'row', - alignItems: 'center', - gap: 10, - maxWidth: '90vw', + fontSize: 25, + fontWeight: 700, + textAlign: 'center', + ...(isEditable && styles.underlinedText), + ...style, }} > - <SvgClose style={{ width: 10, height: 10, flexShrink: 0 }} /> - <Text style={styles.mediumText}>{title}</Text> - </View> - } - placement="top" - triggerRef={tooltipTriggerRef} - isOpen={textOverflowed && isHovered} - offset={10} - > - <Text - innerRef={mergedTextRef} - style={{ - fontSize: textFontSize, - fontWeight: 700, - textAlign: 'center', - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', - ...(isEditable && styles.underlinedText), - ...style, - }} - onClick={_onEdit} - > - {title} - </Text> - </Tooltip> + {title} + </AutoTextSize> + ) : ( + <TextOneLine + onClick={onTitleClick} + style={{ + fontSize: 25, + fontWeight: 700, + textAlign: 'center', + ...(isEditable && styles.underlinedText), + ...style, + }} + > + {title} + </TextOneLine> + )} + </View> ); } diff --git a/packages/desktop-client/src/components/common/Text.tsx b/packages/desktop-client/src/components/common/Text.tsx index 1f9161c78c8f0504f012380d351ad7233ffffe01..71d5be4c00efeac19f139c7f48f6329dc9fc24d2 100644 --- a/packages/desktop-client/src/components/common/Text.tsx +++ b/packages/desktop-client/src/components/common/Text.tsx @@ -1,4 +1,9 @@ -import React, { type HTMLProps, type Ref, type ReactNode } from 'react'; +import React, { + type HTMLProps, + type Ref, + type ReactNode, + forwardRef, +} from 'react'; import { css } from 'glamor'; @@ -11,13 +16,15 @@ type TextProps = HTMLProps<HTMLSpanElement> & { style?: CSSProperties; }; -export const Text = (props: TextProps) => { +export const Text = forwardRef<HTMLSpanElement, TextProps>((props, ref) => { const { className = '', style, innerRef, ...restProps } = props; return ( <span {...restProps} - ref={innerRef} + ref={innerRef ?? ref} className={`${className} ${css(style)}`} /> ); -}; +}); + +Text.displayName = 'Text'; diff --git a/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx b/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx index ed7c7ede8bf8ee317deaec176db0b9d5e8ab0905..09a120fc188a140c7763bb4d574c40dceee8edb3 100644 --- a/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx +++ b/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx @@ -1,6 +1,7 @@ import React, { memo, useRef } from 'react'; import { useDispatch } from 'react-redux'; +import { AutoTextSize } from 'auto-text-size'; import memoizeOne from 'memoize-one'; import { collapseModals, pushModal } from 'loot-core/client/actions'; @@ -10,16 +11,17 @@ 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 { SvgExpandArrow } from '../../../icons/v0'; import { SvgArrowThinLeft, SvgArrowThinRight, - SvgCheveronDown, SvgCheveronRight, } from '../../../icons/v1'; +import { SvgViewShow } from '../../../icons/v2'; import { useResponsive } from '../../../ResponsiveProvider'; import { theme, styles } from '../../../style'; import { BalanceWithCarryover } from '../../budget/BalanceWithCarryover'; -import { makeAmountGrey } from '../../budget/util'; +import { makeAmountFullStyle, makeAmountGrey } from '../../budget/util'; import { Button } from '../../common/Button'; import { Card } from '../../common/Card'; import { Label } from '../../common/Label'; @@ -32,104 +34,176 @@ import { useSheetValue } from '../../spreadsheet/useSheetValue'; import { MOBILE_NAV_HEIGHT } from '../MobileNavTabs'; import { PullToRefresh } from '../PullToRefresh'; -import { ListItem, ROW_HEIGHT } from './ListItem'; +import { ListItem } from './ListItem'; -function ToBudget({ toBudget, onClick }) { +const PILL_STYLE = { + borderRadius: 16, + color: theme.pillText, + backgroundColor: theme.pillBackgroundLight, +}; + +function getColumnWidth({ show3Cols, isSidebar = false, offset = 0 } = {}) { + // If show3Cols = 35vw | 20vw | 20vw | 20vw, + // Else = 45vw | 25vw | 25vw, + if (!isSidebar) { + return show3Cols ? `${20 + offset}vw` : `${25 + offset}vw`; + } + return show3Cols ? `${35 + offset}vw` : `${45 + offset}vw`; +} + +function ToBudget({ toBudget, onClick, show3Cols }) { const amount = useSheetValue(toBudget); + const format = useFormat(); + const sidebarColumnWidth = getColumnWidth({ show3Cols, isSidebar: true }); + return ( - <Button - type="bare" - style={{ flexDirection: 'column', alignItems: 'flex-start' }} - onClick={onClick} + <View + style={{ + flexDirection: 'row', + justifyContent: 'flex-start', + alignItems: 'center', + width: sidebarColumnWidth, + }} > - <Label - title={amount < 0 ? 'Overbudgeted' : 'To Budget'} - style={{ - ...styles.underlinedText, - color: theme.formInputText, - flexShrink: 0, - textAlign: 'left', - }} - /> - <CellValue - binding={toBudget} - type="financial" - style={{ - ...styles.smallText, - fontWeight: '500', - color: amount < 0 ? theme.errorText : theme.formInputText, - }} - /> - </Button> + <Button + type="bare" + style={{ maxWidth: sidebarColumnWidth }} + onClick={onClick} + > + <View> + <Label + title={amount < 0 ? 'Overbudgeted' : 'To Budget'} + style={{ + ...(amount < 0 ? styles.smallText : {}), + color: theme.formInputText, + flexShrink: 0, + textAlign: 'left', + }} + /> + <CellValue + binding={toBudget} + type="financial" + formatter={value => ( + <AutoTextSize + key={value} + as={Text} + minFontSizePx={6} + maxFontSizePx={12} + mode="oneline" + style={{ + maxWidth: sidebarColumnWidth, + fontSize: 12, + fontWeight: '700', + color: amount < 0 ? theme.errorText : theme.formInputText, + }} + > + {format(value, 'financial')} + </AutoTextSize> + )} + /> + </View> + <SvgCheveronRight + style={{ + flexShrink: 0, + color: theme.mobileHeaderTextSubdued, + marginLeft: 5, + }} + width={14} + height={14} + /> + </Button> + </View> ); } -function Saved({ projected, onClick }) { +function Saved({ projected, onClick, show3Cols }) { const binding = projected ? reportBudget.totalBudgetedSaved : reportBudget.totalSaved; const saved = useSheetValue(binding) || 0; + const format = useFormat(); const isNegative = saved < 0; + const sidebarColumnWidth = getColumnWidth({ show3Cols, isSidebar: true }); return ( - <Button - type="bare" + <View style={{ - flexDirection: 'column', - alignItems: 'flex-start', + flexDirection: 'row', + justifyContent: 'flex-start', + alignItems: 'center', + width: sidebarColumnWidth, }} - onClick={onClick} > - {projected ? ( - <> - <Label - title="Projected" - style={{ - ...styles.underlinedText, - color: theme.formInputText, - textAlign: 'left', - letterSpacing: 2, - fontSize: 8, - marginBottom: 0, - }} - /> - <Label - title="Savings" - style={{ - ...styles.underlinedText, - color: theme.formInputText, - textAlign: 'left', - letterSpacing: 2, - fontSize: 8, - }} + <Button + type="bare" + style={{ maxWidth: sidebarColumnWidth }} + onClick={onClick} + > + <View> + <View> + {projected ? ( + <AutoTextSize + as={Label} + minFontSizePx={6} + maxFontSizePx={12} + mode="oneline" + title="Projected Savings" + style={{ + maxWidth: sidebarColumnWidth, + color: theme.formInputText, + textAlign: 'left', + fontSize: 12, + }} + /> + ) : ( + <Label + title={isNegative ? 'Overspent' : 'Saved'} + style={{ + color: theme.formInputText, + textAlign: 'left', + }} + /> + )} + </View> + + <CellValue + binding={binding} + type="financial" + formatter={value => ( + <AutoTextSize + key={value} + as={Text} + minFontSizePx={6} + maxFontSizePx={12} + mode="oneline" + style={{ + maxWidth: sidebarColumnWidth, + fontSize: 12, + fontWeight: '700', + color: projected + ? theme.warningText + : isNegative + ? theme.errorTextDark + : theme.formInputText, + }} + > + {format(value, 'financial')} + </AutoTextSize> + )} /> - </> - ) : ( - <Label - title={isNegative ? 'Overspent' : 'Saved'} + </View> + <SvgCheveronRight style={{ - ...styles.underlinedText, - color: theme.formInputText, - textAlign: 'left', + flexShrink: 0, + color: theme.mobileHeaderTextSubdued, + marginLeft: 5, }} + width={14} + height={14} /> - )} - - <CellValue - binding={binding} - type="financial" - style={{ - ...styles.smallText, - fontWeight: '500', - color: projected - ? theme.warningText - : isNegative - ? theme.errorTextDark - : theme.formInputText, - }} - /> - </Button> + </Button> + </View> ); } @@ -140,6 +214,7 @@ function BudgetCell({ categoryId, month, onBudgetAction, + ...props }) { const dispatch = useDispatch(); const [budgetType = 'rollover'] = useLocalPref('budgetType'); @@ -188,15 +263,10 @@ function BudgetCell({ <CellValue binding={binding} type="financial" - style={{ - textAlign: 'right', - ...styles.smallText, - ...style, - ...styles.underlinedText, - }} getStyle={makeAmountGrey} data-testid={name} onClick={onOpenCategoryBudgetMenu} + {...props} /> ); } @@ -325,19 +395,15 @@ const ExpenseCategory = memo(function ExpenseCategory({ }; const listItemRef = useRef(); - - const _onBudgetAction = (monthIndex, action, arg) => { - onBudgetAction?.( - monthUtils.getMonthFromIndex(monthUtils.getYear(month), monthIndex), - action, - arg, - ); - }; + const format = useFormat(); const navigate = useNavigate(); const onShowActivity = () => { navigate(`/categories/${category.id}?month=${month}`); }; + const sidebarColumnWidth = getColumnWidth({ show3Cols, isSidebar: true }); + const columnWidth = getColumnWidth({ show3Cols }); + const content = ( <ListItem style={{ @@ -350,18 +416,45 @@ const ExpenseCategory = memo(function ExpenseCategory({ data-testid="row" innerRef={listItemRef} > - <View role="button" style={{ flex: 1 }}> - <Text + <View + style={{ + flex: 1, + justifyContent: 'center', + alignItems: 'flex-start', + }} + > + <Button + type="bare" style={{ - ...styles.smallText, - ...styles.underlinedText, - ...styles.lineClamp(2), + maxWidth: sidebarColumnWidth, }} onClick={() => onEdit?.(category.id)} - data-testid="category-name" > - {category.name} - </Text> + <View + style={{ + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'flex-start', + }} + > + <Text + style={{ + ...styles.lineClamp(2), + width: sidebarColumnWidth, + textAlign: 'left', + ...styles.smallText, + }} + data-testid="category-name" + > + {category.name} + </Text> + <SvgCheveronRight + style={{ flexShrink: 0, color: theme.tableTextSubdued }} + width={14} + height={14} + /> + </View> + </Button> </View> <View style={{ @@ -371,37 +464,84 @@ const ExpenseCategory = memo(function ExpenseCategory({ opacity, }} > - <BudgetCell - name="budgeted" - binding={budgeted} + <View style={{ - width: 90, ...(!show3Cols && !showBudgetedCol && { display: 'none' }), + width: columnWidth, + justifyContent: 'center', + alignItems: 'flex-end', }} - categoryId={category.id} - month={month} - onBudgetAction={onBudgetAction} - /> + > + <BudgetCell + name="budgeted" + binding={budgeted} + categoryId={category.id} + month={month} + onBudgetAction={onBudgetAction} + formatter={value => ( + <Button + type="bare" + style={{ + ...PILL_STYLE, + maxWidth: columnWidth, + }} + > + <AutoTextSize + key={value} + as={Text} + minFontSizePx={6} + maxFontSizePx={12} + mode="oneline" + style={{ + maxWidth: columnWidth, + textAlign: 'right', + fontSize: 12, + }} + > + {format(value, 'financial')} + </AutoTextSize> + </Button> + )} + /> + </View> <View style={{ ...(!show3Cols && showBudgetedCol && { display: 'none' }), justifyContent: 'center', alignItems: 'flex-end', - width: 90, - height: ROW_HEIGHT, + width: columnWidth, }} > <CellValue name="spent" binding={spent} - style={{ - ...styles.smallText, - ...styles.underlinedText, - textAlign: 'right', - }} getStyle={makeAmountGrey} type="financial" onClick={onShowActivity} + formatter={value => ( + <Button + type="bare" + style={{ + ...PILL_STYLE, + maxWidth: columnWidth, + }} + > + <AutoTextSize + key={value} + as={Text} + minFontSizePx={6} + maxFontSizePx={12} + mode="oneline" + style={{ + maxWidth: columnWidth, + textAlign: 'right', + fontSize: 12, + }} + > + {format(value, 'financial')} + </AutoTextSize> + </Button> + )} /> </View> <View @@ -409,8 +549,7 @@ const ExpenseCategory = memo(function ExpenseCategory({ ...styles.noTapHighlight, justifyContent: 'center', alignItems: 'flex-end', - width: 90, - height: ROW_HEIGHT, + width: columnWidth, }} > <span role="button" onClick={() => onOpenBalanceMenu?.()}> @@ -419,10 +558,33 @@ const ExpenseCategory = memo(function ExpenseCategory({ balance={balance} goal={goal} budgeted={budgeted} - balanceStyle={{ - ...styles.smallText, - ...styles.underlinedText, - }} + formatter={value => ( + <Button + type="bare" + style={{ + ...PILL_STYLE, + maxWidth: columnWidth, + }} + > + <AutoTextSize + key={value} + as={Text} + minFontSizePx={6} + maxFontSizePx={12} + mode="oneline" + style={{ + maxWidth: columnWidth, + ...makeAmountFullStyle(value, { + zeroColor: theme.pillTextSubdued, + }), + textAlign: 'right', + fontSize: 12, + }} + > + {format(value, 'financial')} + </AutoTextSize> + </Button> + )} /> </span> </View> @@ -479,30 +641,42 @@ const ExpenseGroupHeader = memo(function ExpenseGroupHeader({ }) { const opacity = blank ? 0 : 1; const listItemRef = useRef(); + const format = useFormat(); + const sidebarColumnWidth = getColumnWidth({ + show3Cols, + isSidebar: true, + offset: -3.5, + }); + const columnWidth = getColumnWidth({ show3Cols }); const content = ( <ListItem style={{ flexDirection: 'row', + justifyContent: 'space-between', alignItems: 'center', backgroundColor: theme.tableRowHeaderBackground, opacity: !!group.hidden ? 0.5 : undefined, paddingLeft: 0, }} - data-testid="totals" + data-testid={`expense-group-header-${group.name}`} innerRef={listItemRef} > <View - role="button" style={{ flex: 1, - alignItems: 'center', flexDirection: 'row', + justifyContent: 'flex-start', + width: sidebarColumnWidth, }} > <Button type="bare" - style={{ margin: '0 1px', ...styles.noTapHighlight }} + style={{ + flexShrink: 0, + color: theme.pageTextSubdued, + ...styles.noTapHighlight, + }} activeStyle={{ backgroundColor: 'transparent', }} @@ -511,88 +685,148 @@ const ExpenseGroupHeader = memo(function ExpenseGroupHeader({ }} onClick={() => onToggleCollapse?.(group.id)} > - {collapsed ? ( - <SvgCheveronRight width={14} height={14} /> - ) : ( - <SvgCheveronDown width={14} height={14} /> - )} + <SvgExpandArrow + width={8} + height={8} + style={{ + flexShrink: 0, + transition: 'transform .1s', + transform: collapsed ? 'rotate(-90deg)' : '', + }} + /> </Button> - <Text + <Button + type="bare" style={{ - ...styles.smallText, - ...styles.underlinedText, - ...styles.lineClamp(2), - fontWeight: '500', + maxWidth: sidebarColumnWidth, }} onClick={() => onEdit?.(group.id)} - data-testid="name" > - {group.name} - </Text> + <View + style={{ + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'flex-start', + }} + > + <Text + style={{ + ...styles.lineClamp(2), + width: sidebarColumnWidth, + textAlign: 'left', + ...styles.smallText, + }} + data-testid="group-name" + > + {group.name} + </Text> + <SvgCheveronRight + style={{ flexShrink: 0, color: theme.tableTextSubdued }} + width={14} + height={14} + /> + </View> + </Button> </View> <View style={{ flexDirection: 'row', - justifyContent: 'center', + justifyContent: 'flex-end', alignItems: 'center', - height: ROW_HEIGHT, opacity, + paddingRight: 5, }} > <View style={{ ...(!show3Cols && !showBudgetedCol && { display: 'none' }), - width: 90, - height: ROW_HEIGHT, + width: columnWidth, justifyContent: 'center', alignItems: 'flex-end', }} > <CellValue binding={budgeted} - style={{ - ...styles.smallText, - fontWeight: '500', - textAlign: 'right', - }} type="financial" + formatter={value => ( + <AutoTextSize + key={value} + as={Text} + minFontSizePx={6} + maxFontSizePx={12} + mode="oneline" + style={{ + maxWidth: columnWidth, + fontSize: 12, + fontWeight: '500', + paddingLeft: 5, + textAlign: 'right', + }} + > + {format(value, 'financial')} + </AutoTextSize> + )} /> </View> <View style={{ ...(!show3Cols && showBudgetedCol && { display: 'none' }), - width: 90, - height: ROW_HEIGHT, + width: columnWidth, justifyContent: 'center', alignItems: 'flex-end', }} > <CellValue binding={spent} - style={{ - ...styles.smallText, - fontWeight: '500', - textAlign: 'right', - }} type="financial" + formatter={value => ( + <AutoTextSize + key={value} + as={Text} + minFontSizePx={6} + maxFontSizePx={12} + mode="oneline" + style={{ + maxWidth: columnWidth, + fontSize: 12, + fontWeight: '500', + paddingLeft: 5, + textAlign: 'right', + }} + > + {format(value, 'financial')} + </AutoTextSize> + )} /> </View> <View style={{ - width: 90, - height: ROW_HEIGHT, + width: columnWidth, justifyContent: 'center', alignItems: 'flex-end', }} > <CellValue binding={balance} - style={{ - ...styles.smallText, - fontWeight: '500', - textAlign: 'right', - }} type="financial" + formatter={value => ( + <AutoTextSize + key={value} + as={Text} + minFontSizePx={6} + maxFontSizePx={12} + mode="oneline" + style={{ + maxWidth: columnWidth, + fontSize: 12, + fontWeight: '500', + paddingLeft: 5, + textAlign: 'right', + }} + > + {format(value, 'financial')} + </AutoTextSize> + )} /> </View> </View> @@ -637,30 +871,38 @@ const IncomeGroupHeader = memo(function IncomeGroupHeader({ onToggleCollapse, }) { const listItemRef = useRef(); + const format = useFormat(); + const sidebarColumnWidth = getColumnWidth({ isSidebar: true, offset: -13.5 }); + const columnWidth = getColumnWidth(); return ( <ListItem style={{ flexDirection: 'row', + justifyContent: 'space-between', alignItems: 'center', backgroundColor: theme.tableRowHeaderBackground, opacity: !!group.hidden ? 0.5 : undefined, paddingLeft: 0, }} innerRef={listItemRef} + data-testid={`income-group-header-${group.name}`} > <View - role="button" style={{ flex: 1, - alignItems: 'center', flexDirection: 'row', - height: ROW_HEIGHT, + justifyContent: 'flex-start', + width: sidebarColumnWidth, }} > <Button type="bare" - style={{ margin: '0 1px', ...styles.noTapHighlight }} + style={{ + flexShrink: 0, + color: theme.pageTextSubdued, + ...styles.noTapHighlight, + }} activeStyle={{ backgroundColor: 'transparent', }} @@ -669,62 +911,119 @@ const IncomeGroupHeader = memo(function IncomeGroupHeader({ }} onClick={() => onToggleCollapse?.(group.id)} > - {collapsed ? ( - <SvgCheveronRight width={14} height={14} /> - ) : ( - <SvgCheveronDown width={14} height={14} /> - )} + <SvgExpandArrow + width={8} + height={8} + style={{ + flexShrink: 0, + transition: 'transform .1s', + transform: collapsed ? 'rotate(-90deg)' : '', + }} + /> </Button> - <Text + <Button + type="bare" style={{ - ...styles.smallText, - ...styles.underlinedText, - ...styles.lineClamp(2), - fontWeight: '500', + maxWidth: sidebarColumnWidth, }} onClick={() => onEdit?.(group.id)} - data-testid="name" > - {group.name} - </Text> + <View + style={{ + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'flex-start', + }} + > + <Text + style={{ + ...styles.lineClamp(2), + width: sidebarColumnWidth, + textAlign: 'left', + ...styles.smallText, + }} + data-testid="group-name" + > + {group.name} + </Text> + <SvgCheveronRight + style={{ flexShrink: 0, color: theme.tableTextSubdued }} + width={14} + height={14} + /> + </View> + </Button> </View> - {budgeted && ( + <View + style={{ + flexDirection: 'row', + justifyContent: 'flex-end', + alignItems: 'center', + paddingRight: 5, + }} + > + {budgeted && ( + <View + style={{ + justifyContent: 'center', + alignItems: 'flex-end', + width: columnWidth, + }} + > + <CellValue + binding={budgeted} + type="financial" + formatter={value => ( + <AutoTextSize + key={value} + as={Text} + minFontSizePx={6} + maxFontSizePx={12} + mode="oneline" + style={{ + maxWidth: columnWidth, + paddingLeft: 5, + textAlign: 'right', + fontSize: 12, + fontWeight: '500', + }} + > + {format(value, 'financial')} + </AutoTextSize> + )} + /> + </View> + )} <View style={{ justifyContent: 'center', alignItems: 'flex-end', - width: 90, - height: ROW_HEIGHT, + width: columnWidth, }} > <CellValue - binding={budgeted} - style={{ - ...styles.smallText, - textAlign: 'right', - fontWeight: '500', - }} + binding={balance} type="financial" + formatter={value => ( + <AutoTextSize + key={value} + as={Text} + minFontSizePx={6} + maxFontSizePx={12} + mode="oneline" + style={{ + maxWidth: columnWidth, + paddingLeft: 5, + textAlign: 'right', + fontSize: 12, + fontWeight: '500', + }} + > + {format(value, 'financial')} + </AutoTextSize> + )} /> </View> - )} - <View - style={{ - justifyContent: 'center', - alignItems: 'flex-end', - width: 90, - height: ROW_HEIGHT, - }} - > - <CellValue - binding={balance} - style={{ - ...styles.smallText, - textAlign: 'right', - fontWeight: '500', - }} - type="financial" - /> </View> </ListItem> ); @@ -741,11 +1040,15 @@ const IncomeCategory = memo(function IncomeCategory({ onBudgetAction, }) { const listItemRef = useRef(); + const format = useFormat(); + const sidebarColumnWidth = getColumnWidth({ isSidebar: true, offset: -10 }); + const columnWidth = getColumnWidth(); return ( <ListItem style={{ flexDirection: 'row', + justifyContent: 'space-between', alignItems: 'center', backgroundColor: 'transparent', borderBottomWidth: 0, @@ -753,55 +1056,124 @@ const IncomeCategory = memo(function IncomeCategory({ opacity: !!category.hidden ? 0.5 : undefined, ...style, }} + data-testid="row" innerRef={listItemRef} > <View - role="button" style={{ flex: 1, justifyContent: 'center', alignItems: 'flex-start', - height: ROW_HEIGHT, + width: sidebarColumnWidth, }} > - <Text + <Button + type="bare" style={{ - ...styles.smallText, - ...styles.underlinedText, - ...styles.lineClamp(2), + maxWidth: sidebarColumnWidth, }} onClick={() => onEdit?.(category.id)} - data-testid="name" > - {category.name} - </Text> + <View + style={{ + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'flex-start', + }} + > + <Text + style={{ + ...styles.lineClamp(2), + width: sidebarColumnWidth, + textAlign: 'left', + ...styles.smallText, + }} + data-testid="category-name" + > + {category.name} + </Text> + <SvgCheveronRight + style={{ flexShrink: 0, color: theme.tableTextSubdued }} + width={14} + height={14} + /> + </View> + </Button> </View> - {budgeted && ( - <BudgetCell - name="budgeted" - binding={budgeted} - style={{ width: 90 }} - categoryId={category.id} - month={month} - onBudgetAction={onBudgetAction} - /> - )} <View style={{ - justifyContent: 'center', - alignItems: 'flex-end', - width: 90, - height: ROW_HEIGHT, + flexDirection: 'row', + justifyContent: 'flex-end', + alignItems: 'center', }} > - <CellValue - binding={balance} + {budgeted && ( + <View + style={{ + width: columnWidth, + justifyContent: 'center', + alignItems: 'flex-end', + }} + > + <BudgetCell + name="budgeted" + binding={budgeted} + categoryId={category.id} + month={month} + onBudgetAction={onBudgetAction} + formatter={value => ( + <Button + type="bare" + style={{ ...PILL_STYLE, maxWidth: columnWidth }} + > + <AutoTextSize + key={value} + as={Text} + minFontSizePx={6} + maxFontSizePx={12} + mode="oneline" + style={{ + maxWidth: columnWidth, + textAlign: 'right', + fontSize: 12, + }} + > + {format(value, 'financial')} + </AutoTextSize> + </Button> + )} + /> + </View> + )} + <View style={{ - ...styles.smallText, - textAlign: 'right', + justifyContent: 'center', + alignItems: 'flex-end', + width: columnWidth, + paddingRight: 5, }} - type="financial" - /> + > + <CellValue + binding={balance} + type="financial" + formatter={value => ( + <AutoTextSize + key={value} + as={Text} + minFontSizePx={6} + maxFontSizePx={12} + mode="oneline" + style={{ + maxWidth: columnWidth, + textAlign: 'right', + fontSize: 12, + }} + > + {format(value, 'financial')} + </AutoTextSize> + )} + /> + </View> </View> </ListItem> ); @@ -995,6 +1367,7 @@ function IncomeGroup({ collapsed, onToggleCollapse, }) { + const columnWidth = getColumnWidth(); return ( <View> <View @@ -1004,11 +1377,13 @@ function IncomeGroup({ justifyContent: 'flex-end', marginTop: 50, marginBottom: 5, - marginRight: 14, + marginRight: 15, }} > - {type === 'report' && <Label title="Budgeted" style={{ width: 90 }} />} - <Label title="Received" style={{ width: 90 }} /> + {type === 'report' && ( + <Label title="Budgeted" style={{ width: columnWidth }} /> + )} + <Label title="Received" style={{ width: columnWidth }} /> </View> <Card style={{ marginTop: 0 }}> @@ -1184,26 +1559,18 @@ export function BudgetTable({ const show3Cols = width >= 360; // let editMode = false; // neuter editMode -- sorry, not rewriting drag-n-drop right now - const format = useFormat(); const [showSpentColumn = false, setShowSpentColumnPref] = useLocalPref( 'mobile.showSpentColumn', ); - const [showHiddenCategories = false] = useLocalPref( - 'budget.showHiddenCategories', - ); - - function toggleDisplay() { + function toggleSpentColumn() { setShowSpentColumnPref(!showSpentColumn); } - const buttonStyle = { - padding: 0, - backgroundColor: 'transparent', - borderRadius: 'unset', - }; - + const [showHiddenCategories = false] = useLocalPref( + 'budget.showHiddenCategories', + ); const noBackgroundColorStyle = { backgroundColor: 'transparent', color: 'white', @@ -1235,123 +1602,239 @@ export function BudgetTable({ onClick={() => onOpenBudgetPageMenu?.()} > <SvgLogo width="20" height="20" /> + <SvgCheveronRight + style={{ flexShrink: 0, color: theme.mobileHeaderTextSubdued }} + width="14" + height="14" + /> </Button> } /> } + > + <BudgetTableHeader + type={type} + month={month} + show3Cols={show3Cols} + showSpentColumn={showSpentColumn} + toggleSpentColumn={toggleSpentColumn} + onShowBudgetSummary={onShowBudgetSummary} + /> + <PullToRefresh onRefresh={onRefresh}> + <View + data-testid="budget-table" + style={{ + backgroundColor: theme.pageBackground, + paddingBottom: MOBILE_NAV_HEIGHT, + }} + > + <BudgetGroups + type={type} + categoryGroups={categoryGroups} + showBudgetedCol={!showSpentColumn} + show3Cols={show3Cols} + showHiddenCategories={showHiddenCategories} + month={month} + // gestures={gestures} + // editMode={editMode} + onEditGroup={onEditGroup} + onEditCategory={onEditCategory} + onSaveCategory={onSaveCategory} + onDeleteCategory={onDeleteCategory} + onAddCategory={onAddCategory} + onSaveGroup={onSaveGroup} + onDeleteGroup={onDeleteGroup} + onReorderCategory={onReorderCategory} + onReorderGroup={onReorderGroup} + onBudgetAction={onBudgetAction} + /> + </View> + </PullToRefresh> + </Page> + ); +} + +function BudgetTableHeader({ + show3Cols, + type, + month, + onShowBudgetSummary, + showSpentColumn, + toggleSpentColumn, +}) { + const format = useFormat(); + const buttonStyle = { + padding: 0, + backgroundColor: 'transparent', + borderRadius: 'unset', + }; + const sidebarColumnWidth = getColumnWidth({ show3Cols, isSidebar: true }); + const columnWidth = getColumnWidth({ show3Cols }); + return ( + <View + style={{ + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + flexShrink: 0, + padding: '10px 15px', + paddingLeft: 10, + backgroundColor: theme.tableRowHeaderBackground, + borderBottomWidth: 1, + borderColor: theme.tableBorder, + }} > <View style={{ + width: sidebarColumnWidth, flexDirection: 'row', - flexShrink: 0, - padding: 10, - paddingRight: 14, - backgroundColor: theme.tableRowHeaderBackground, - borderBottomWidth: 1, - borderColor: theme.tableBorder, + justifyContent: 'flex-start', + alignItems: 'center', }} > {type === 'report' ? ( <Saved projected={month >= monthUtils.currentMonth()} onClick={onShowBudgetSummary} + show3Cols={show3Cols} /> ) : ( <ToBudget toBudget={rolloverBudget.toBudget} onClick={onShowBudgetSummary} + show3Cols={show3Cols} /> )} - <View style={{ flex: 1 }} /> + </View> + <View + style={{ + flexDirection: 'row', + justifyContent: 'flex-end', + alignItems: 'center', + }} + > {(show3Cols || !showSpentColumn) && ( - <Button - type="bare" - disabled={show3Cols} - onClick={toggleDisplay} + <View style={{ - ...buttonStyle, - padding: '0 8px', - margin: '0 -8px', - background: - !showSpentColumn && !show3Cols - ? `linear-gradient(-45deg, ${theme.formInputBackgroundSelection} 8px, transparent 0)` - : null, + width: columnWidth, + alignItems: 'flex-end', }} > - <View - style={{ - flexBasis: 90, - width: 90, - justifyContent: 'center', - alignItems: 'flex-end', - }} + <Button + type="bare" + disabled={show3Cols} + onClick={toggleSpentColumn} + style={buttonStyle} > - <Label - title="Budgeted" - style={{ color: theme.buttonNormalText }} - /> - <CellValue - binding={ - type === 'report' - ? reportBudget.totalBudgetedExpense - : rolloverBudget.totalBudgeted - } - type="financial" - style={{ - ...styles.smallText, - color: theme.buttonNormalText, - textAlign: 'right', - fontWeight: '500', - }} - formatter={value => { - return format(-parseFloat(value || '0'), 'financial'); - }} - /> - </View> - </Button> + <View style={{ alignItems: 'flex-end' }}> + <View style={{ flexDirection: 'row', alignItems: 'center' }}> + {!show3Cols && ( + <SvgViewShow + width={12} + height={12} + style={{ + color: theme.pageTextSubdued, + marginRight: 5, + }} + /> + )} + <Label + title="Budgeted" + style={{ color: theme.formInputText }} + /> + </View> + <CellValue + binding={ + type === 'report' + ? reportBudget.totalBudgetedExpense + : rolloverBudget.totalBudgeted + } + type="financial" + formatter={value => ( + <AutoTextSize + key={value} + as={Text} + minFontSizePx={6} + maxFontSizePx={12} + mode="oneline" + style={{ + maxWidth: columnWidth, + color: theme.formInputText, + paddingLeft: 5, + textAlign: 'right', + fontSize: 12, + fontWeight: '500', + }} + > + {format(value, 'financial')} + </AutoTextSize> + )} + /> + </View> + </Button> + </View> )} {(show3Cols || showSpentColumn) && ( - <Button - type="bare" - disabled={show3Cols} - onClick={toggleDisplay} + <View style={{ - ...buttonStyle, - background: - showSpentColumn && !show3Cols - ? `linear-gradient(45deg, ${theme.formInputBackgroundSelection} 8px, transparent 0)` - : null, + width: columnWidth, + alignItems: 'flex-end', }} > - <View - style={{ - width: 90, - justifyContent: 'center', - alignItems: 'flex-end', - }} + <Button + type="bare" + disabled={show3Cols} + onClick={toggleSpentColumn} + style={buttonStyle} > - <Label title="Spent" style={{ color: theme.formInputText }} /> - <CellValue - binding={ - type === 'report' - ? reportBudget.totalSpent - : rolloverBudget.totalSpent - } - type="financial" - style={{ - ...styles.smallText, - color: theme.formInputText, - textAlign: 'right', - fontWeight: '500', - }} - /> - </View> - </Button> + <View style={{ alignItems: 'flex-end' }}> + <View style={{ flexDirection: 'row', alignItems: 'center' }}> + {!show3Cols && ( + <SvgViewShow + width={12} + height={12} + style={{ + color: theme.pageTextSubdued, + marginRight: 5, + }} + /> + )} + <Label title="Spent" style={{ color: theme.formInputText }} /> + </View> + <CellValue + binding={ + type === 'report' + ? reportBudget.totalSpent + : rolloverBudget.totalSpent + } + type="financial" + formatter={value => ( + <AutoTextSize + key={value} + as={Text} + minFontSizePx={6} + maxFontSizePx={12} + mode="oneline" + style={{ + maxWidth: columnWidth, + color: theme.formInputText, + paddingLeft: 5, + textAlign: 'right', + fontSize: 12, + fontWeight: '500', + }} + > + {format(value, 'financial')} + </AutoTextSize> + )} + /> + </View> + </Button> + </View> )} <View style={{ - width: 90, - justifyContent: 'center', + width: columnWidth, alignItems: 'flex-end', }} > @@ -1363,46 +1846,29 @@ export function BudgetTable({ : rolloverBudget.totalBalance } type="financial" - style={{ - ...styles.smallText, - color: theme.formInputText, - textAlign: 'right', - fontWeight: '500', - }} + formatter={value => ( + <AutoTextSize + key={value} + as={Text} + minFontSizePx={6} + maxFontSizePx={12} + mode="oneline" + style={{ + maxWidth: columnWidth, + color: theme.formInputText, + paddingLeft: 5, + textAlign: 'right', + fontSize: 12, + fontWeight: '500', + }} + > + {format(value, 'financial')} + </AutoTextSize> + )} /> </View> </View> - <PullToRefresh onRefresh={onRefresh}> - <View - data-testid="budget-table" - style={{ - backgroundColor: theme.pageBackground, - paddingBottom: MOBILE_NAV_HEIGHT, - }} - > - <BudgetGroups - type={type} - categoryGroups={categoryGroups} - showBudgetedCol={!showSpentColumn} - show3Cols={show3Cols} - showHiddenCategories={showHiddenCategories} - month={month} - // gestures={gestures} - // editMode={editMode} - onEditGroup={onEditGroup} - onEditCategory={onEditCategory} - onSaveCategory={onSaveCategory} - onDeleteCategory={onDeleteCategory} - onAddCategory={onAddCategory} - onSaveGroup={onSaveGroup} - onDeleteGroup={onDeleteGroup} - onReorderCategory={onReorderCategory} - onReorderGroup={onReorderGroup} - onBudgetAction={onBudgetAction} - /> - </View> - </PullToRefresh> - </Page> + </View> ); } diff --git a/packages/desktop-client/src/components/mobile/budget/ListItem.tsx b/packages/desktop-client/src/components/mobile/budget/ListItem.tsx index 8eef9483dcee8f652956827d9a756b2dfdce049a..99c577dd2a2707fb46f0db615bb216bf19c01200 100644 --- a/packages/desktop-client/src/components/mobile/budget/ListItem.tsx +++ b/packages/desktop-client/src/components/mobile/budget/ListItem.tsx @@ -3,7 +3,7 @@ import React, { type ComponentProps, type ReactNode } from 'react'; import { type CSSProperties, theme } from '../../../style'; import { View } from '../../common/View'; -export const ROW_HEIGHT = 50; +const ROW_HEIGHT = 50; type ListItemProps = ComponentProps<typeof View> & { children?: ReactNode; @@ -19,8 +19,8 @@ export const ListItem = ({ children, style, ...props }: ListItemProps) => { borderColor: theme.tableBorder, flexDirection: 'row', alignItems: 'center', - paddingLeft: 10, - paddingRight: 10, + paddingLeft: 5, + paddingRight: 5, zIndex: 1, ...style, }} diff --git a/packages/desktop-client/src/components/modals/EditRule.jsx b/packages/desktop-client/src/components/modals/EditRule.jsx index 104829ec36bba8085a5544af95088c353c0ace98..ad80a18ffcda5c39ad6c7e88277f0127d385d34f 100644 --- a/packages/desktop-client/src/components/modals/EditRule.jsx +++ b/packages/desktop-client/src/components/modals/EditRule.jsx @@ -1006,7 +1006,7 @@ export function EditRule({ modalProps, defaultRule, onSave: originalOnSave }) { > <Text style={{ - ...styles.verySmallText, + ...styles.smallText, marginBottom: '10px', }} > diff --git a/packages/desktop-client/src/components/modals/ReportBalanceMenuModal.tsx b/packages/desktop-client/src/components/modals/ReportBalanceMenuModal.tsx index c77f4776d12019305ff30cbdbfd994eee8f13dae..681e3b1ebfdf329d4d5c3e178211f2da3540dd47 100644 --- a/packages/desktop-client/src/components/modals/ReportBalanceMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/ReportBalanceMenuModal.tsx @@ -6,7 +6,7 @@ import { useCategory } from '../../hooks/useCategory'; import { type CSSProperties, theme, styles } from '../../style'; import { BalanceWithCarryover } from '../budget/BalanceWithCarryover'; import { BalanceMenu } from '../budget/report/BalanceMenu'; -import { Modal } from '../common/Modal'; +import { Modal, ModalTitle } from '../common/Modal'; import { Text } from '../common/Text'; import { View } from '../common/View'; import { type CommonModalProps } from '../Modals'; @@ -37,7 +37,7 @@ export function ReportBalanceMenuModal({ return ( <Modal - title={category.name} + title={<ModalTitle title={category.name} shrinkOnOverflow />} showHeader focusAfterClose={false} {...modalProps} @@ -59,7 +59,7 @@ export function ReportBalanceMenuModal({ </Text> <BalanceWithCarryover disabled - balanceStyle={{ + style={{ textAlign: 'center', ...styles.veryLargeText, }} diff --git a/packages/desktop-client/src/components/modals/ReportBudgetMenuModal.tsx b/packages/desktop-client/src/components/modals/ReportBudgetMenuModal.tsx index a76187e2f90f4fa1747549ac383d8d52b08539a8..13ac4bd665764394cc79fee38c67b410d5937c4a 100644 --- a/packages/desktop-client/src/components/modals/ReportBudgetMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/ReportBudgetMenuModal.tsx @@ -10,7 +10,7 @@ import { amountToInteger, integerToAmount } from 'loot-core/shared/util'; import { useCategory } from '../../hooks/useCategory'; import { type CSSProperties, theme, styles } from '../../style'; import { BudgetMenu } from '../budget/report/BudgetMenu'; -import { Modal } from '../common/Modal'; +import { Modal, ModalTitle } from '../common/Modal'; import { Text } from '../common/Text'; import { View } from '../common/View'; import { FocusableAmountInput } from '../mobile/transactions/FocusableAmountInput'; @@ -58,14 +58,10 @@ export function ReportBudgetMenuModal({ return ( <Modal - title={category.name} + title={<ModalTitle title={category.name} shrinkOnOverflow />} showHeader focusAfterClose={false} {...modalProps} - style={{ - padding: '0 10px', - paddingBottom: 10, - }} > <View style={{ diff --git a/packages/desktop-client/src/components/modals/RolloverBalanceMenuModal.tsx b/packages/desktop-client/src/components/modals/RolloverBalanceMenuModal.tsx index 068c86b1f720079ac499bdee652b9be685265a6c..4132d2eeace976eba30cd307e504d655322e8be2 100644 --- a/packages/desktop-client/src/components/modals/RolloverBalanceMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/RolloverBalanceMenuModal.tsx @@ -6,7 +6,7 @@ import { useCategory } from '../../hooks/useCategory'; import { type CSSProperties, theme, styles } from '../../style'; import { BalanceWithCarryover } from '../budget/BalanceWithCarryover'; import { BalanceMenu } from '../budget/rollover/BalanceMenu'; -import { Modal } from '../common/Modal'; +import { Modal, ModalTitle } from '../common/Modal'; import { Text } from '../common/Text'; import { View } from '../common/View'; import { type CommonModalProps } from '../Modals'; @@ -39,7 +39,7 @@ export function RolloverBalanceMenuModal({ return ( <Modal - title={category.name} + title={<ModalTitle title={category.name} shrinkOnOverflow />} showHeader focusAfterClose={false} {...modalProps} @@ -61,7 +61,7 @@ export function RolloverBalanceMenuModal({ </Text> <BalanceWithCarryover disabled - balanceStyle={{ + style={{ textAlign: 'center', ...styles.veryLargeText, }} diff --git a/packages/desktop-client/src/components/modals/RolloverBudgetMenuModal.tsx b/packages/desktop-client/src/components/modals/RolloverBudgetMenuModal.tsx index b64090c92b1b535b52e8afb33097d82c258eaa62..d54587acc991d6859d5f7e4846e9cea8ff19d379 100644 --- a/packages/desktop-client/src/components/modals/RolloverBudgetMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/RolloverBudgetMenuModal.tsx @@ -10,7 +10,7 @@ import { amountToInteger, integerToAmount } from 'loot-core/shared/util'; import { useCategory } from '../../hooks/useCategory'; import { type CSSProperties, theme, styles } from '../../style'; import { BudgetMenu } from '../budget/rollover/BudgetMenu'; -import { Modal } from '../common/Modal'; +import { Modal, ModalTitle } from '../common/Modal'; import { Text } from '../common/Text'; import { View } from '../common/View'; import { FocusableAmountInput } from '../mobile/transactions/FocusableAmountInput'; @@ -58,14 +58,10 @@ export function RolloverBudgetMenuModal({ return ( <Modal - title={category.name} + title={<ModalTitle title={category.name} shrinkOnOverflow />} showHeader focusAfterClose={false} {...modalProps} - style={{ - padding: '0 10px', - paddingBottom: 10, - }} > <View style={{ diff --git a/packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx b/packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx index 23692dff3e818051e65f0361465e6640c5908493..46c31083f3798a415a197ecef7477e1af1c18877 100644 --- a/packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/ScheduledTransactionMenuModal.tsx @@ -6,7 +6,7 @@ import { type Query } from 'loot-core/shared/query'; import { type CSSProperties, theme, styles } from '../../style'; import { Menu } from '../common/Menu'; -import { Modal } from '../common/Modal'; +import { Modal, ModalTitle } from '../common/Modal'; import { Text } from '../common/Text'; import { View } from '../common/View'; import { type CommonModalProps } from '../Modals'; @@ -42,7 +42,7 @@ export function ScheduledTransactionMenuModal({ return ( <Modal - title={schedule.name} + title={<ModalTitle title={schedule.name || ''} shrinkOnOverflow />} showHeader focusAfterClose={false} {...modalProps} diff --git a/packages/desktop-client/src/components/rules/RuleRow.tsx b/packages/desktop-client/src/components/rules/RuleRow.tsx index 6548e8fd5d789b98c755247133706d120856c67d..4b8a748a83c52713936f37b208e62224bf9e7873 100644 --- a/packages/desktop-client/src/components/rules/RuleRow.tsx +++ b/packages/desktop-client/src/components/rules/RuleRow.tsx @@ -134,7 +134,7 @@ export const RuleRow = memo( > <Text style={{ - ...styles.verySmallText, + ...styles.smallText, color: theme.pageTextLight, marginBottom: 6, }} diff --git a/packages/desktop-client/src/style/styles.ts b/packages/desktop-client/src/style/styles.ts index e630ac4f48e037a29007caac60f907548684c65c..d9bd91203bd9708bf124e9783631a7aa95a01479 100644 --- a/packages/desktop-client/src/style/styles.ts +++ b/packages/desktop-client/src/style/styles.ts @@ -54,7 +54,7 @@ export const styles = { fontSize: 13, }, verySmallText: { - fontSize: 13, + fontSize: 12, }, tinyText: { fontSize: 10, diff --git a/packages/desktop-client/src/style/themes/dark.ts b/packages/desktop-client/src/style/themes/dark.ts index 24271183578bbe9725a849f1f8721e1be55edd0b..8932bfbc7c9f9d68bbcc1fce12bf6e9e5f0929d0 100644 --- a/packages/desktop-client/src/style/themes/dark.ts +++ b/packages/desktop-client/src/style/themes/dark.ts @@ -185,6 +185,7 @@ export const pillBorderDark = pillBorder; export const pillBackgroundSelected = colorPalette.purple600; export const pillTextSelected = colorPalette.navy150; export const pillBorderSelected = colorPalette.purple400; +export const pillTextSubdued = colorPalette.navy500; export const reportsRed = colorPalette.red300; export const reportsBlue = colorPalette.blue400; diff --git a/packages/desktop-client/src/style/themes/development.ts b/packages/desktop-client/src/style/themes/development.ts index c4f71d3df5ef0b4a777803e916b74c2db4ab38ab..d369f5037a90f10a902bbb26f1ee8cc3b653cd60 100644 --- a/packages/desktop-client/src/style/themes/development.ts +++ b/packages/desktop-client/src/style/themes/development.ts @@ -176,7 +176,7 @@ export const checkboxBorderSelected = colorPalette.blue500; export const checkboxShadowSelected = colorPalette.blue300; export const pillBackground = colorPalette.navy150; -export const pillBackgroundLight = pillBackground; +export const pillBackgroundLight = colorPalette.navy50; export const pillText = colorPalette.navy800; export const pillTextHighlighted = colorPalette.purple600; export const pillBorder = colorPalette.navy150; @@ -184,6 +184,7 @@ export const pillBorderDark = colorPalette.navy300; export const pillBackgroundSelected = colorPalette.blue150; export const pillTextSelected = colorPalette.blue900; export const pillBorderSelected = colorPalette.purple500; +export const pillTextSubdued = colorPalette.navy200; export const reportsRed = colorPalette.red300; export const reportsBlue = colorPalette.blue400; diff --git a/packages/desktop-client/src/style/themes/light.ts b/packages/desktop-client/src/style/themes/light.ts index 294122cf31d6e34b956600d0d0672e1740482544..dd3812f881b7dc858b5a888c8f7c3630c752c7fb 100644 --- a/packages/desktop-client/src/style/themes/light.ts +++ b/packages/desktop-client/src/style/themes/light.ts @@ -179,7 +179,7 @@ export const checkboxShadowSelected = colorPalette.blue300; export const checkboxToggleBackground = colorPalette.gray400; export const pillBackground = colorPalette.navy150; -export const pillBackgroundLight = colorPalette.navy100; +export const pillBackgroundLight = colorPalette.navy50; export const pillText = colorPalette.navy800; export const pillTextHighlighted = colorPalette.purple600; export const pillBorder = colorPalette.navy150; @@ -187,6 +187,7 @@ export const pillBorderDark = colorPalette.navy300; export const pillBackgroundSelected = colorPalette.blue150; export const pillTextSelected = colorPalette.blue900; export const pillBorderSelected = colorPalette.purple500; +export const pillTextSubdued = colorPalette.navy200; export const reportsRed = colorPalette.red300; export const reportsBlue = colorPalette.blue400; diff --git a/packages/desktop-client/src/style/themes/midnight.ts b/packages/desktop-client/src/style/themes/midnight.ts index ee1ff3097c02d8e70c6362fbddbc495457bc442b..9116d3a4aa2f077c0cf17c0ce1b1e93da1f404f3 100644 --- a/packages/desktop-client/src/style/themes/midnight.ts +++ b/packages/desktop-client/src/style/themes/midnight.ts @@ -187,6 +187,7 @@ export const pillBorderDark = pillBorder; export const pillBackgroundSelected = colorPalette.purple600; export const pillTextSelected = colorPalette.gray150; export const pillBorderSelected = colorPalette.purple300; +export const pillTextSubdued = colorPalette.gray500; export const reportsRed = colorPalette.red300; export const reportsBlue = colorPalette.blue400; diff --git a/upcoming-release-notes/2642.md b/upcoming-release-notes/2642.md new file mode 100644 index 0000000000000000000000000000000000000000..6dd6da3aa9a44a688514d228232ea2f0c55090d0 --- /dev/null +++ b/upcoming-release-notes/2642.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [joel-jeremy] +--- + +Mobile budget page revamp. diff --git a/yarn.lock b/yarn.lock index 5bc6445e8720b67c98751e3b8ca56165b69213d3..b8b91f59e6f185467a9474e605bb17722d655fb4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -85,6 +85,7 @@ __metadata: "@use-gesture/react": "npm:^10.3.0" "@vitejs/plugin-basic-ssl": "npm:^1.1.0" "@vitejs/plugin-react-swc": "npm:^3.6.0" + auto-text-size: "npm:^0.2.3" chokidar: "npm:^3.5.3" cross-env: "npm:^7.0.3" date-fns: "npm:^2.30.0" @@ -6710,6 +6711,15 @@ __metadata: languageName: node linkType: hard +"auto-text-size@npm:^0.2.3": + version: 0.2.3 + resolution: "auto-text-size@npm:0.2.3" + peerDependencies: + react: "*" + checksum: 229f20d6801d074ee4dfa542eac41d9a6e95880761f79f6fc4c31bebfc35bd045d6aeb8dc8bdc846154ce8d28e10e9bbb013a5b574f6a3254027988b789b1514 + languageName: node + linkType: hard + "available-typed-arrays@npm:^1.0.5": version: 1.0.5 resolution: "available-typed-arrays@npm:1.0.5"