diff --git a/.claude/skills/app-coding-standards/SKILL.md b/.claude/skills/app-coding-standards/SKILL.md index bef97f04d42f..376bac8bd283 100644 --- a/.claude/skills/app-coding-standards/SKILL.md +++ b/.claude/skills/app-coding-standards/SKILL.md @@ -74,6 +74,7 @@ Coding standards for the Expensify App. Each standard is a standalone file in `r - [UI-1](rules/ui-1-correct-loading-indicator.md) — Use the correct loading indicator based on navigation context - [UI-2](rules/ui-2-new-page-scrollview.md) — New pages must be scrollable - [UI-3](rules/ui-3-no-inline-styles.md) — Do not use inline style objects +- [UI-4](rules/ui-4-layout-spacing-tokens.md) — Type and responsive insets come from tokens ## Usage diff --git a/.claude/skills/app-coding-standards/rules/ui-4-layout-spacing-tokens.md b/.claude/skills/app-coding-standards/rules/ui-4-layout-spacing-tokens.md new file mode 100644 index 000000000000..f7ea99c52cdd --- /dev/null +++ b/.claude/skills/app-coding-standards/rules/ui-4-layout-spacing-tokens.md @@ -0,0 +1,59 @@ +--- +ruleId: UI-4 +title: Type and responsive insets come from tokens +--- + +## [UI-4] Type and responsive insets come from tokens + +### Reasoning + +Per `STYLING.md`, font sizes come from `` and paddings or margins that differ between narrow and wide layouts come from `useLayoutSpacing()`. Writing `shouldUseNarrowLayout ? styles.ph5 : styles.ph8` decides the card inset at the call site, so every card row has to agree by hand and a design change means editing dozens of files. `rulesdir/no-raw-typography` and `rulesdir/no-layout-spacing-conditional` enforce both. + +### Incorrect + +```tsx +function CardRow() { + const {shouldUseNarrowLayout} = useResponsiveLayout(); + return ( + + Title + + ); +} +``` + +### Correct + +```tsx +function CardRow() { + const {cardPaddingHorizontal} = useLayoutSpacing(); + return ( + + Title + + ); +} +``` + +See `useLayoutSpacing`'s implementation for available options. Tokens live in `src/styles/layoutSpacing.ts`; add a new one there rather than writing a ternary. + +--- + +### Review Metadata + +Flag ONLY when ALL of these are true: + +- The changed code picks a horizontal inset helper by layout flag where the pair spells the card padding values (the `5` step narrow, the `8` step wide, e.g. `styles.ph5 : styles.ph8`, `styles.mhn5 : styles.mhn8`), or sets a raw `fontSize` / `lineHeight` +- A matching token exists (`cardPadding*`, `pageGutter*`, or a `Text` variant) + +**DO NOT flag if:** + +- The conditional picks vertical spacing only (`pt`, `pb`, `pv`, `mt`, `mb`, `mv`, `gap`) +- The values are not a card inset or page gutter and no token exists yet (suggest adding one instead) +- The code is inside `src/styles/` + +**Search Patterns** (hints for reviewers): +- `shouldUseNarrowLayout ? styles.p` +- `shouldUseNarrowLayout ? styles.m` +- `isSmallScreenWidth ? styles.p` +- `fontSize:` diff --git a/CLAUDE.md b/CLAUDE.md index b83dc7430e44..1340a6c1dfde 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -63,6 +63,7 @@ Do not use `useMemo`, `useCallback`, or `React.memo` in components or hooks that ### Code Quality - **ESLint**: Linter. Pre-existing violations are grandfathered via the seatbelt ratchet in `scripts/lint/`. +- **Design tokens**: font sizes come from ``, and paddings or margins that differ between narrow and wide layouts come from `useLayoutSpacing()` (`cardPaddingHorizontal`, `pageGutter`, ...). Never write `shouldUseNarrowLayout ? styles.ph5 : styles.ph8` or a raw `fontSize`. See `contributingGuides/STYLING.md`. ### Post-Edit Checklist (IMPORTANT) diff --git a/config/eslint/eslint.config.mjs b/config/eslint/eslint.config.mjs index 10e73863e75b..9b6129fdbc7d 100644 --- a/config/eslint/eslint.config.mjs +++ b/config/eslint/eslint.config.mjs @@ -312,6 +312,7 @@ const config = defineConfig([ 'rulesdir/require-a11y-disable-justification': 'error', 'rulesdir/no-direct-pre-insert-fullscreen-under-rhp': 'error', 'rulesdir/no-raw-typography': 'error', + 'rulesdir/no-layout-spacing-conditional': 'error', 'rulesdir/no-direct-personal-details-list': 'error', 'rulesdir/require-locale-for-localized-date-format': 'error', 'rulesdir/prefer-narrow-hook-dependencies': [ diff --git a/contributingGuides/STYLING.md b/contributingGuides/STYLING.md index e8126e8e50f6..87f2c74de94e 100644 --- a/contributingGuides/STYLING.md +++ b/contributingGuides/STYLING.md @@ -17,6 +17,25 @@ These helper styles are loosely based on the [Bootstrap system of CSS utility he **Note:** Not all helpers from Bootstrap exist, so it may be necessary to create the helper style we need. +## Type and Responsive Spacing Come From Tokens + +Two kinds of values must never be written by hand in a component: font sizes and the paddings or margins that change between narrow and wide layouts. + +- **Type**: use `` (see `src/styles/typography.ts`). Raw `fontSize` / `lineHeight` are blocked by `rulesdir/no-raw-typography`. +- **Responsive insets**: use `useLayoutSpacing()` (see `src/styles/layoutSpacing.ts`). It resolves `cardPadding` and `pageGutter` for the current layout and returns ready styles (`cardGap`, the space between stacked cards, is also read directly by the style sheet for the Home layout). Writing the card inset by hand, `shouldUseNarrowLayout ? styles.ph5 : styles.ph8` or any other `5`/`8` inset pair, is blocked by `rulesdir/no-layout-spacing-conditional`. + +```tsx +// Bad - the card inset is decided here, so every card row has to agree by hand +const {shouldUseNarrowLayout} = useResponsiveLayout(); + + +// Good - the inset is a token, resolved once for the current layout +const {cardPaddingHorizontal} = useLayoutSpacing(); + +``` + +Spacing that is not a card inset or page gutter (for example a `pb2` vs `pb5` rhythm tweak) is still written with the helper styles. If you need a new responsive inset, add it to `layoutSpacing.ts` rather than writing the ternary. + ## When to Create a New Style If we need some minimal set of styling rules applied to a single-use component, then it's almost always better to use an array of helper styles rather than create an entirely new style if it will only be used once. Resist the urge to create a new style for every new element added to a screen. There is a very good chance the style we are adding is a "single-use" style. diff --git a/eslint-plugin-local-rules/no-layout-spacing-conditional.js b/eslint-plugin-local-rules/no-layout-spacing-conditional.js new file mode 100644 index 000000000000..e8637bc9b13b --- /dev/null +++ b/eslint-plugin-local-rules/no-layout-spacing-conditional.js @@ -0,0 +1,113 @@ +const name = 'no-layout-spacing-conditional'; + +const meta = { + type: 'problem', + docs: { + description: + 'Disallow spelling the card inset by hand as a narrow/wide ternary over spacing helpers. Card padding must come from useLayoutSpacing() so every screen agrees on the same values.', + recommended: 'error', + }, + schema: [], + messages: { + layoutSpacingConditional: + '`{{test}} ? styles.{{first}} : styles.{{second}}` spells the card inset by hand. Use the matching style from `useLayoutSpacing()` (src/styles/layoutSpacing.ts) so the value stays in one place.', + layoutSpacingConditionalKnown: 'Replace `{{test}} ? styles.{{first}} : styles.{{second}}` with `{{replacement}}` from `useLayoutSpacing()` (src/styles/layoutSpacing.ts).', + }, +}; + +const NARROW_FLAGS = new Set(['shouldUseNarrowLayout', 'isSmallScreenWidth', 'isExtraSmallScreenWidth']); +const WIDE_FLAGS = new Set(['isLargeScreenWidth', 'isExtraLargeScreenWidth']); +const KNOWN_REPLACEMENTS = { + 'ph5:ph8': 'cardPaddingHorizontal', + 'p5:p8': 'cardPadding', + 'mhn5:mhn8': 'cardEdgeToEdge', + 'pl5:pl8': 'cardPaddingLeft', +}; +const SPACING_CLASS_PATTERN = /^([pm](?:h|l|r|hn|ln|rn)?)(\d+)$/; +const CARD_PADDING_STEPS = {narrow: '5', wide: '8'}; +const TS_WRAPPER_TYPES = new Set(['TSAsExpression', 'TSSatisfiesExpression', 'TSNonNullExpression', 'TSTypeAssertion']); + +function unwrap(node) { + if (TS_WRAPPER_TYPES.has(node.type)) { + return unwrap(node.expression); + } + return node; +} + +function getSpacingClassName(node) { + let unwrapped = unwrap(node); + if (unwrapped.type === 'MemberExpression' && !unwrapped.computed && unwrapped.property.type === 'Identifier' && /^(padding|margin)/.test(unwrapped.property.name)) { + unwrapped = unwrap(unwrapped.object); + } + if (unwrapped.type !== 'MemberExpression' || unwrapped.computed || unwrapped.property.type !== 'Identifier') { + return undefined; + } + return SPACING_CLASS_PATTERN.test(unwrapped.property.name) ? unwrapped.property.name : undefined; +} + +function findSpacingClassName(node) { + const unwrapped = unwrap(node); + if (unwrapped.type === 'ArrayExpression') { + for (const element of unwrapped.elements) { + const className = element ? findSpacingClassName(element) : undefined; + if (className) { + return className; + } + } + return undefined; + } + return getSpacingClassName(unwrapped); +} + +function getLayoutFlag(test) { + const unwrapped = unwrap(test); + if (unwrapped.type === 'UnaryExpression' && unwrapped.operator === '!') { + const inner = getLayoutFlag(unwrapped.argument); + return inner ? {name: `!${inner.name}`, isNarrowWhenTrue: !inner.isNarrowWhenTrue} : undefined; + } + let flagName; + if (unwrapped.type === 'Identifier') { + flagName = unwrapped.name; + } else if (unwrapped.type === 'MemberExpression' && !unwrapped.computed && unwrapped.property.type === 'Identifier') { + flagName = unwrapped.property.name; + } + if (NARROW_FLAGS.has(flagName)) { + return {name: flagName, isNarrowWhenTrue: true}; + } + if (WIDE_FLAGS.has(flagName)) { + return {name: flagName, isNarrowWhenTrue: false}; + } + return undefined; +} + +function create(context) { + return { + ConditionalExpression(node) { + const flag = getLayoutFlag(node.test); + if (!flag) { + return; + } + const first = findSpacingClassName(node.consequent); + const second = findSpacingClassName(node.alternate); + if (!first || !second) { + return; + } + const narrow = flag.isNarrowWhenTrue ? first : second; + const wide = flag.isNarrowWhenTrue ? second : first; + const [, narrowPrefix, narrowStep] = SPACING_CLASS_PATTERN.exec(narrow); + const [, widePrefix, wideStep] = SPACING_CLASS_PATTERN.exec(wide); + if (narrowPrefix !== widePrefix || narrowStep !== CARD_PADDING_STEPS.narrow || wideStep !== CARD_PADDING_STEPS.wide) { + return; + } + const replacement = KNOWN_REPLACEMENTS[`${narrow}:${wide}`]; + const data = {test: flag.name, first, second}; + if (replacement) { + context.report({node, messageId: 'layoutSpacingConditionalKnown', data: {...data, replacement}}); + return; + } + context.report({node, messageId: 'layoutSpacingConditional', data}); + }, + }; +} + +export {name, meta, create}; diff --git a/src/components/BaseWidgetItem.tsx b/src/components/BaseWidgetItem.tsx index eda476825450..29465338e200 100644 --- a/src/components/BaseWidgetItem.tsx +++ b/src/components/BaseWidgetItem.tsx @@ -1,4 +1,4 @@ -import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -39,7 +39,7 @@ type BaseWidgetItemProps = { function BaseWidgetItem({icon, title, subtitle, ctaText, onCtaPress, buttonVariant}: BaseWidgetItemProps) { const styles = useThemeStyles(); const theme = useTheme(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {cardPaddingHorizontal} = useLayoutSpacing(); return ( {({hovered}) => ( - + - {children} + {children} ); diff --git a/src/components/MoneyReportHeaderActions/index.tsx b/src/components/MoneyReportHeaderActions/index.tsx index dd23a7573b59..a564fad4a815 100644 --- a/src/components/MoneyReportHeaderActions/index.tsx +++ b/src/components/MoneyReportHeaderActions/index.tsx @@ -4,6 +4,7 @@ import {useMoneyReportTransactionThread} from '@components/MoneyReportTransactio import {useSearchSelectionActions, useSearchSelectionContext} from '@components/Search/SearchContext'; import useExportAgainModal from '@hooks/useExportAgainModal'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useResponsiveLayoutOnWideRHP from '@hooks/useResponsiveLayoutOnWideRHP'; @@ -37,6 +38,7 @@ function narrowPrimaryAction(primaryAction: MoneyReportHeaderActionsProps['prima function MoneyReportHeaderActions({reportID, primaryAction, isReportInSearch, backTo}: MoneyReportHeaderActionsProps) { const styles = useThemeStyles(); + const {pageGutter} = useLayoutSpacing(); const dropdownMenuRef = useRef(null) as React.RefObject; const {shouldUseNarrowLayout, isMediumScreenWidth, isInLandscapeMode} = useResponsiveLayout(); @@ -73,7 +75,7 @@ function MoneyReportHeaderActions({reportID, primaryAction, isReportInSearch, ba // actions stay in the header but are disabled while a selection is being built up. if (shouldDisableReportActions && shouldUseNarrowLayoutOnWideRHP) { return ( - + + {!!primaryAction && ( {showPendingExpensePlaceholder && ( - + diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx index e68460b18b8b..39cfa7edcbef 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx @@ -18,6 +18,8 @@ import {getTransactionPendingAction} from '@libs/TransactionUtils'; import Navigation from '@navigation/Navigation'; +import {resolveLayoutSpacing} from '@styles/layoutSpacing'; + import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -204,6 +206,7 @@ function MoneyRequestReportTransactionList({ // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {isSmallScreenWidth} = useResponsiveLayout(); const {shouldUseNarrowLayout} = useResponsiveLayoutOnWideRHP(); + const {pageGutter} = resolveLayoutSpacing(shouldUseNarrowLayout); const navigateToTransactionThread = useNavigateToTransactionThread(); const longPressModalRef = useRef(null); const {isOffline} = useNetwork(); @@ -312,7 +315,7 @@ function MoneyRequestReportTransactionList({ pendingAction: undefined, }; return ( - + + ) : ( - + {shouldShowGroupedTransactions && ( payBulkSelectedItem(subItem, triggerKYCFlow)} variant={CONST.BUTTON_VARIANT.SUCCESS} isSplitButton={false} - style={[styles.w100, styles.ph5]} + style={[styles.w100, pageGutter]} anchorAlignment={{ horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, diff --git a/src/components/Search/SearchChartWrapper.tsx b/src/components/Search/SearchChartWrapper.tsx index f6399e824d4d..8f1bb857dfcd 100644 --- a/src/components/Search/SearchChartWrapper.tsx +++ b/src/components/Search/SearchChartWrapper.tsx @@ -1,7 +1,7 @@ import {ChartHeader} from '@components/Charts'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import React from 'react'; @@ -13,13 +13,13 @@ import CHART_GROUP_BY_CONFIG from './chartGroupByConfig'; function SearchChartWrapper({children, title, groupBy}: {children: React.ReactNode; title?: string; groupBy: SearchGroupBy}) { const styles = useThemeStyles(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {cardPadding} = useLayoutSpacing(); const icons = useMemoizedLazyExpensifyIcons(['Users', 'CreditCard', 'Send', 'Folder', 'Basket', 'Tag', 'Calendar']); const {titleIconName} = CHART_GROUP_BY_CONFIG[groupBy]; const titleIcon = icons[titleIconName]; return ( - + ({ const [transactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportActionItem?.childReportID}`); const [chatReportStable] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(reportStable?.chatReportID)}`, {selector: getStableReportSelector}); const styles = useThemeStyles(); + const {pageGutterMargin} = useLayoutSpacing(); const StyleUtils = useStyleUtils(); const {isSelected} = useRowSelection(item.keyForList); const pressableStyle = [ @@ -70,7 +72,7 @@ function ChatListItem({ onDismissError={onDismissError} onFocus={onFocus} shouldSyncFocus={shouldSyncFocus} - pressableWrapperStyle={[styles.mh5, StyleUtils.getSearchRowBackgroundStyle(isSelected), styles.br2]} + pressableWrapperStyle={[pageGutterMargin, StyleUtils.getSearchRowBackgroundStyle(isSelected), styles.br2]} hoverStyle={isSelected && styles.activeComponentBG} > ({ }: ExpenseReportListItemProps) { const reportItem = item as unknown as ExpenseReportListItemType; const styles = useThemeStyles(); + const {pageGutterMargin} = useLayoutSpacing(); const StyleUtils = useStyleUtils(); const theme = useTheme(); /* @@ -485,7 +487,7 @@ function ExpenseReportListItemInner({ shouldSyncFocus={shouldSyncFocus} hoverStyle={isSelected && styles.activeComponentBG} pressableWrapperStyle={[ - styles.mh5, + pageGutterMargin, StyleUtils.getSearchRowBackgroundStyle(isSelected), !isLargeScreenWidth && styles.br0, isPendingDelete && styles.cursorDisabled, diff --git a/src/components/Search/SearchList/ListItem/GroupChildrenContainer.tsx b/src/components/Search/SearchList/ListItem/GroupChildrenContainer.tsx index 047b9d8000ec..ccc1d4b4e23b 100644 --- a/src/components/Search/SearchList/ListItem/GroupChildrenContainer.tsx +++ b/src/components/Search/SearchList/ListItem/GroupChildrenContainer.tsx @@ -1,4 +1,5 @@ import useExpandCollapseAnimation from '@hooks/useExpandCollapseAnimation'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -33,6 +34,7 @@ function GroupChildrenContainer({ newTransactionID, }: GroupChildrenContainerProps) { const styles = useThemeStyles(); + const {pageGutterMargin} = useLayoutSpacing(); const StyleUtils = useStyleUtils(); const hasBorder = !isFirstItem; const {isRendered, animatedStyle, onLayout} = useExpandCollapseAnimation(isExpanded, isExpanded && hasBorder, item.keyForList); @@ -48,7 +50,9 @@ function GroupChildrenContainer({ } return ( - + {isContentVisible ? ( ({item, isFocused, showTooltip, onSelectRow, onFocus, shouldSyncFocus, isLastItem, isFirstItem}: ListItemProps) { const reportItem = item as unknown as ExpenseReportListItemType; const styles = useThemeStyles(); + const {pageGutterMargin} = useLayoutSpacing(); const StyleUtils = useStyleUtils(); const isSelected = item.isSelected ?? false; @@ -43,7 +45,7 @@ function SearchMergeReportsListItem({item, isFocused, sh shouldSyncFocus={shouldSyncFocus} hoverStyle={isSelected && styles.activeComponentBG} pressableWrapperStyle={[ - styles.mh5, + pageGutterMargin, isFirstItem && styles.tableTopRadius, isLastItem && styles.tableBottomRadius, !isLastItem && StyleUtils.getSelectedBorderBottomStyle(isSelected), diff --git a/src/components/Search/SearchList/ListItem/TaskListItem.tsx b/src/components/Search/SearchList/ListItem/TaskListItem.tsx index bda6e047162f..bc409a6176a5 100644 --- a/src/components/Search/SearchList/ListItem/TaskListItem.tsx +++ b/src/components/Search/SearchList/ListItem/TaskListItem.tsx @@ -2,6 +2,7 @@ import {useRowSelection} from '@components/Search/SearchSelectionProvider'; import ListItemComposed from '@components/SelectionList/ListItemComposed'; import type {ListItem} from '@components/SelectionList/types'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -49,6 +50,7 @@ function TaskListItem({ const liveTaskItem: TaskListItemType = liveParentReportAttributeName && liveParentReportAttributeName !== taskItem.parentReportName ? {...taskItem, parentReportName: liveParentReportAttributeName} : taskItem; const styles = useThemeStyles(); + const {pageGutterMargin} = useLayoutSpacing(); const StyleUtils = useStyleUtils(); const {isLargeScreenWidth} = useResponsiveLayout(); @@ -88,7 +90,7 @@ function TaskListItem({ shouldSyncFocus={shouldSyncFocus} hoverStyle={isSelected && styles.activeComponentBG} pressableWrapperStyle={[ - styles.mh5, + pageGutterMargin, StyleUtils.getSearchRowBackgroundStyle(isSelected), !isLargeScreenWidth && styles.br2, isLargeScreenWidth && isLastItem && [styles.tableBottomRadius, styles.overflowHidden], diff --git a/src/components/Search/SearchList/ListItem/TransactionGroupListItem.tsx b/src/components/Search/SearchList/ListItem/TransactionGroupListItem.tsx index 71f23fa30c3b..53712be05413 100644 --- a/src/components/Search/SearchList/ListItem/TransactionGroupListItem.tsx +++ b/src/components/Search/SearchList/ListItem/TransactionGroupListItem.tsx @@ -7,6 +7,7 @@ import type {SearchGroupBy} from '@components/Search/types'; import type {ListItem} from '@components/SelectionList/types'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -106,6 +107,7 @@ function TransactionGroupListItemImpl({ const theme = useTheme(); const styles = useThemeStyles(); + const {pageGutterMargin} = useLayoutSpacing(); const {currentSearchResults} = useSearchResultsContext(); const {isLargeScreenWidth} = useResponsiveLayout(); const currentUserDetails = useCurrentUserPersonalDetails(); @@ -558,7 +560,7 @@ function TransactionGroupListItemImpl({ ]} onFocus={onFocus} wrapperStyle={[ - styles.mh5, + pageGutterMargin, StyleUtils.getSearchRowBackgroundStyle(!!isItemSelected), styles.userSelectNone, isLargeScreenWidth diff --git a/src/components/Search/SearchList/ListItem/TransactionListItem/TransactionListItemNarrow.tsx b/src/components/Search/SearchList/ListItem/TransactionListItem/TransactionListItemNarrow.tsx index 2b2357434f20..a10f4d0b1d45 100644 --- a/src/components/Search/SearchList/ListItem/TransactionListItem/TransactionListItemNarrow.tsx +++ b/src/components/Search/SearchList/ListItem/TransactionListItem/TransactionListItemNarrow.tsx @@ -6,6 +6,7 @@ import {useRowSelection} from '@components/Search/SearchSelectionProvider'; import type {ListItem} from '@components/SelectionList/types'; import TransactionItemRow from '@components/TransactionItemRow'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import useStyleUtils from '@hooks/useStyleUtils'; import useSyncFocus from '@hooks/useSyncFocus'; import useTheme from '@hooks/useTheme'; @@ -46,6 +47,7 @@ function TransactionListItemNarrow({ isAttendeesEnabledForMovingPolicy, }: TransactionListItemNarrowProps) { const styles = useThemeStyles(); + const {pageGutterMargin} = useLayoutSpacing(); const theme = useTheme(); const StyleUtils = useStyleUtils(); const pressableRef = useRef>(null); @@ -98,7 +100,7 @@ function TransactionListItemNarrow({ ]} onFocus={onFocus} wrapperStyle={[ - styles.mh5, + pageGutterMargin, styles.flex1, StyleUtils.getSearchRowBackgroundStyle(isSelected), styles.userSelectNone, diff --git a/src/components/Search/SearchList/ListItem/TransactionListItem/TransactionListItemWide.tsx b/src/components/Search/SearchList/ListItem/TransactionListItem/TransactionListItemWide.tsx index ee40ba62f5d5..ec0cfe74cc33 100644 --- a/src/components/Search/SearchList/ListItem/TransactionListItem/TransactionListItemWide.tsx +++ b/src/components/Search/SearchList/ListItem/TransactionListItem/TransactionListItemWide.tsx @@ -6,6 +6,7 @@ import {useRowSelection} from '@components/Search/SearchSelectionProvider'; import type {ListItem} from '@components/SelectionList/types'; import TransactionItemRow from '@components/TransactionItemRow'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import useStyleUtils from '@hooks/useStyleUtils'; import useSyncFocus from '@hooks/useSyncFocus'; import useTheme from '@hooks/useTheme'; @@ -51,6 +52,7 @@ function TransactionListItemWide({ chatReport, }: TransactionListItemWideProps) { const styles = useThemeStyles(); + const {pageGutterMargin} = useLayoutSpacing(); const theme = useTheme(); const StyleUtils = useStyleUtils(); const pressableRef = useRef>(null); @@ -166,7 +168,7 @@ function TransactionListItemWide({ ]} onFocus={onFocus} wrapperStyle={[ - styles.mh5, + pageGutterMargin, styles.flex1, StyleUtils.getSearchRowBackgroundStyle(isSelected), styles.userSelectNone, diff --git a/src/components/Search/SearchPageHeader/SearchFiltersBarNarrow.tsx b/src/components/Search/SearchPageHeader/SearchFiltersBarNarrow.tsx index dbd865eb672d..d96fa0081beb 100644 --- a/src/components/Search/SearchPageHeader/SearchFiltersBarNarrow.tsx +++ b/src/components/Search/SearchPageHeader/SearchFiltersBarNarrow.tsx @@ -1,6 +1,7 @@ import type {SearchQueryJSON} from '@components/Search/types'; import SearchFiltersSkeleton from '@components/Skeletons/SearchFiltersSkeleton'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import useThemeStyles from '@hooks/useThemeStyles'; import type {SearchFilter} from '@libs/SearchUIUtils'; @@ -21,6 +22,7 @@ type SearchFiltersBarNarrowProps = { function SearchFiltersBarNarrow({queryJSON}: SearchFiltersBarNarrowProps) { const styles = useThemeStyles(); + const {pageGutter} = useLayoutSpacing(); const scrollRef = useRef>(null); const {filters, hasErrors, shouldShowFiltersBarLoading, canReset, canSave, resetFilters} = useSearchFiltersBar(queryJSON); @@ -45,7 +47,7 @@ function SearchFiltersBarNarrow({queryJSON}: SearchFiltersBarNarrowProps) { horizontal keyboardShouldPersistTaps="always" style={[styles.flexRow, styles.overflowScroll, styles.flexGrow0, (!!data.length || canSave || canReset) && styles.mb4]} - contentContainerStyle={[styles.flexRow, styles.flexGrow0, styles.gap2, styles.ph5, styles.alignItemsCenter]} + contentContainerStyle={[styles.flexRow, styles.flexGrow0, styles.gap2, pageGutter, styles.alignItemsCenter]} ref={scrollRef} showsHorizontalScrollIndicator={false} data={data} diff --git a/src/components/Search/SearchStaticList.tsx b/src/components/Search/SearchStaticList.tsx index 71ba1ee2501a..0d6d736cd237 100644 --- a/src/components/Search/SearchStaticList.tsx +++ b/src/components/Search/SearchStaticList.tsx @@ -7,6 +7,7 @@ import TransactionItemRow from '@components/TransactionItemRow'; import {useCurrencyListActions} from '@hooks/useCurrencyList'; import useDelegateAccountID from '@hooks/useDelegateAccountID'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -76,6 +77,7 @@ function SearchStaticList({ columns = DEFAULT_COLUMNS, }: SearchStaticListProps) { const styles = useThemeStyles(); + const {pageGutterMargin} = useLayoutSpacing(); const theme = useTheme(); const StyleUtils = useStyleUtils(); const {translate, localeCompare, formatPhoneNumber, dateFnsLocale} = useLocalize(); @@ -205,7 +207,7 @@ function SearchStaticList({ > {!shouldUseNarrowLayout && columns.length > 0 && ( - + {canSelectMultiple && ( + {banner} {cardLayout === CARD_LAYOUT.ICON_ON_TOP && ( )} - + {cardLayout === CARD_LAYOUT.ICON_ON_LEFT && ( ( <> 0; @@ -78,7 +80,7 @@ export default function TableFilterBar({label, shouldShowResetFiltersButton, chi } return ( - + diff --git a/src/components/Table/TableHeader.tsx b/src/components/Table/TableHeader.tsx index a6dee01b20e8..10fb6c091dd1 100644 --- a/src/components/Table/TableHeader.tsx +++ b/src/components/Table/TableHeader.tsx @@ -3,6 +3,7 @@ import Icon from '@components/Icon'; import {PressableWithFeedback} from '@components/Pressable'; import Text from '@components/Text'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -70,6 +71,7 @@ function TableHeader): {gridTemplateColumns: string[] | undefined; scrollWidth: number | undefined} { const styles = useThemeStyles(); + const {values} = useLayoutSpacing(); const noDynamicWidths = {gridTemplateColumns: undefined, scrollWidth: undefined}; @@ -137,7 +139,7 @@ function useDynamicColumnWidths {shouldShowBulkActions && ( - + variant={CONST.BUTTON_VARIANT.SUCCESS} onPress={() => {}} diff --git a/src/components/WidgetContainer.tsx b/src/components/WidgetContainer.tsx index e2a5b075450f..502499283478 100644 --- a/src/components/WidgetContainer.tsx +++ b/src/components/WidgetContainer.tsx @@ -1,4 +1,4 @@ -import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -27,11 +27,11 @@ type WidgetContainerProps = { function WidgetContainer({children, title, titleContent, containerStyles, titleRightContent}: WidgetContainerProps) { const styles = useThemeStyles(); const theme = useTheme(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {values} = useLayoutSpacing(); return ( - + {titleContent ?? (!!title && {title})} diff --git a/src/components/WorkspaceEmptyStateSection.tsx b/src/components/WorkspaceEmptyStateSection.tsx index b4106bcc283f..522f522349ad 100644 --- a/src/components/WorkspaceEmptyStateSection.tsx +++ b/src/components/WorkspaceEmptyStateSection.tsx @@ -1,3 +1,4 @@ +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -34,12 +35,15 @@ type WorkspaceEmptyStateSectionProps = { function WorkspaceEmptyStateSection({icon, subtitle, title, containerStyle, shouldStyleAsCard = true, subtitleComponent}: WorkspaceEmptyStateSectionProps) { const styles = useThemeStyles(); const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {pageGutterMargin, cardGapBottom} = useLayoutSpacing(); return ( + + {shouldShowBulkActionsButton ? ( variant={CONST.BUTTON_VARIANT.SUCCESS} diff --git a/src/pages/DynamicRoomMembersPage.tsx b/src/pages/DynamicRoomMembersPage.tsx index c68b5376b1ca..c2d72666f3cb 100644 --- a/src/pages/DynamicRoomMembersPage.tsx +++ b/src/pages/DynamicRoomMembersPage.tsx @@ -15,6 +15,7 @@ import useCleanupSelectedOptions from '@hooks/useCleanupSelectedOptions'; import useConfirmModal from '@hooks/useConfirmModal'; import useDynamicBackPath from '@hooks/useDynamicBackPath'; import useFilteredSelection from '@hooks/useFilteredSelection'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; @@ -135,6 +136,7 @@ function DynamicRoomMembersPage({report, policy}: DynamicRoomMembersPageProps) { // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to use the selection mode only on small screens // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout(); + const {pageGutter} = useLayoutSpacing(); const isMobileSelectionModeEnabled = useMobileSelectionMode(); const canSelectMultiple = isSmallScreenWidth ? isMobileSelectionModeEnabled : true; const isLoading = !isPersonalDetailsReady(personalDetails) || !didLoadRoomMembers; @@ -335,7 +337,7 @@ function DynamicRoomMembersPage({report, policy}: DynamicRoomMembersPageProps) { }, [bulkActionsButtonOptions, inviteUser, isSmallScreenWidth, selectedMembers.length, styles, translate, canSelectMultiple, shouldUseNarrowLayout, icons.Plus]); const selectionModeHeader = isMobileSelectionModeEnabled && isSmallScreenWidth; - const tableHeaderComponent = {headerButtons}; + const tableHeaderComponent = {headerButtons}; let subtitleKey: '' | TranslationPaths | undefined; if (!isEmptyObject(report)) { diff --git a/src/pages/Insights/InsightsDashboard.tsx b/src/pages/Insights/InsightsDashboard.tsx index ee11a1be7697..f4673a15d8b0 100644 --- a/src/pages/Insights/InsightsDashboard.tsx +++ b/src/pages/Insights/InsightsDashboard.tsx @@ -7,6 +7,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useMultipleSnapshots from '@hooks/useMultipleSnapshots'; @@ -60,6 +61,7 @@ type InsightsDashboardContentProps = { function InsightsDashboardContent({state, headlineChart, supportingCharts, filters, onRetry, onGroupByChange}: InsightsDashboardContentProps) { const styles = useThemeStyles(); + const {pageGutter} = useLayoutSpacing(); const theme = useTheme(); const {translate} = useLocalize(); const {shouldUseNarrowLayout} = useResponsiveLayout(); @@ -122,10 +124,10 @@ function InsightsDashboardContent({state, headlineChart, supportingCharts, filte return ( - + - + {columns.map((columnCharts, columnIndex) => ( {columnCharts.map(({chart, queryJSON, snapshot}) => ( } {state === INSIGHTS_CHART_STATE.EMPTY && } {(state === INSIGHTS_CHART_STATE.LOADING || state === INSIGHTS_CHART_STATE.READY) && ( - + ; @@ -45,7 +45,7 @@ function InsightsDataTable({rows, isLoading}: InsightsDataTableProps) { } return ( - + {rows.map(({item, point, color}) => ( {controls} @@ -70,7 +72,7 @@ function InsightsPageControls({filters, defaultFilters, onChange}: InsightsPageC } return ( - + {controls} ); diff --git a/src/pages/ReimbursementAccount/NonUSD/Finish/index.tsx b/src/pages/ReimbursementAccount/NonUSD/Finish/index.tsx index 62e44574cc30..73b365b6a17f 100644 --- a/src/pages/ReimbursementAccount/NonUSD/Finish/index.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/Finish/index.tsx @@ -61,7 +61,7 @@ function Finish() {
{translate('finishStep.thanksFor')} @@ -77,7 +77,6 @@ function Finish() { title={translate('finishStep.enable2FA')} icon={illustrations.ShieldYellow} titleStyles={[styles.mb4, styles.textHeadline]} - containerStyles={[styles.mh5]} > {translate('finishStep.weTake')} diff --git a/src/pages/ReimbursementAccount/USD/ConnectBankAccount/components/Enable2FACard.tsx b/src/pages/ReimbursementAccount/USD/ConnectBankAccount/components/Enable2FACard.tsx index d12c82197199..e1e5158cfb69 100644 --- a/src/pages/ReimbursementAccount/USD/ConnectBankAccount/components/Enable2FACard.tsx +++ b/src/pages/ReimbursementAccount/USD/ConnectBankAccount/components/Enable2FACard.tsx @@ -27,7 +27,6 @@ function Enable2FACard() { title={translate('connectBankAccountStep.enable2FATitle')} icon={ShieldYellow} titleStyles={styles.mb4} - containerStyles={styles.mh5} > {translate('connectBankAccountStep.enable2FAText')} diff --git a/src/pages/ReimbursementAccount/USD/ConnectBankAccount/components/FinishChatCard.tsx b/src/pages/ReimbursementAccount/USD/ConnectBankAccount/components/FinishChatCard.tsx index e1f53dd44121..22dcdc007160 100644 --- a/src/pages/ReimbursementAccount/USD/ConnectBankAccount/components/FinishChatCard.tsx +++ b/src/pages/ReimbursementAccount/USD/ConnectBankAccount/components/FinishChatCard.tsx @@ -9,7 +9,6 @@ import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import {usePersonalDetailsByIDs} from '@hooks/usePersonalDetails'; import useResetBankAccountModal from '@hooks/useResetBankAccountModal'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; @@ -51,7 +50,6 @@ type FinishChatCardProps = { function FinishChatCard({requiresTwoFactorAuth, reimbursementAccount, policy, setUSDBankAccountStep, backTo}: FinishChatCardProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); const [conciergePersonalDetails] = usePersonalDetailsByIDs([CONST.ACCOUNT_ID.CONCIERGE]); @@ -83,11 +81,11 @@ function FinishChatCard({requiresTwoFactorAuth, reimbursementAccount, policy, se
{translate('connectBankAccountStep.letsChatText')} - + - + {!shouldDisplayButtonsInSeparateLine && headerContent} - {shouldDisplayButtonsInSeparateLine && !!headerContent && {headerContent}} + {shouldDisplayButtonsInSeparateLine && !!headerContent && {headerContent}} {!shouldDisplayButtonsInSeparateLine && !!headerContent && {headerContent}} - {shouldDisplayButtonsInSeparateLine && !!headerContent && {headerContent}} + {shouldDisplayButtonsInSeparateLine && !!headerContent && {headerContent}} {!shouldDisplayButtonsInSeparateLine && {createGroupHeaderButton}} - {shouldDisplayButtonsInSeparateLine && {createGroupHeaderButton}} + {shouldDisplayButtonsInSeparateLine && {createGroupHeaderButton}} - + - + { @@ -47,7 +47,7 @@ function GettingStartedRow({item}: GettingStartedRowProps) { sentryLabel={CONST.SENTRY_LABEL.HOME_PAGE.GETTING_STARTED_ROW} > {({hovered}) => ( - + ))} - + diff --git a/src/pages/home/HomeSectionExpandToggle.tsx b/src/pages/home/HomeSectionExpandToggle.tsx index 25b2823b4cda..f3ad6f838057 100644 --- a/src/pages/home/HomeSectionExpandToggle.tsx +++ b/src/pages/home/HomeSectionExpandToggle.tsx @@ -2,9 +2,9 @@ import Icon from '@components/Icon'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import Text from '@components/Text'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -34,7 +34,7 @@ function HomeSectionExpandToggle({isExpanded, onPress, collapsedLabel, wrapperSt const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {cardPaddingHorizontal} = useLayoutSpacing(); const icons = useMemoizedLazyExpensifyIcons(['DownArrow', 'UpArrow']); const label = isExpanded ? translate('common.showLess') : collapsedLabel; @@ -45,7 +45,7 @@ function HomeSectionExpandToggle({isExpanded, onPress, collapsedLabel, wrapperSt role={CONST.ROLE.BUTTON} accessibilityLabel={label} sentryLabel="HomeSectionExpandToggle" - style={[styles.flexRow, styles.alignItemsCenter, styles.gap3, styles.pv3, shouldUseNarrowLayout ? styles.ph5 : styles.ph8, wrapperStyle]} + style={[styles.flexRow, styles.alignItemsCenter, styles.gap3, styles.pv3, cardPaddingHorizontal, wrapperStyle]} > - + {title} {rows.length > 0 && ( diff --git a/src/pages/home/InsightsSection/InsightsSectionContent.tsx b/src/pages/home/InsightsSection/InsightsSectionContent.tsx index 17bef3d19335..5e8212fb8ba8 100644 --- a/src/pages/home/InsightsSection/InsightsSectionContent.tsx +++ b/src/pages/home/InsightsSection/InsightsSectionContent.tsx @@ -5,6 +5,7 @@ import SearchChartView from '@components/Search/SearchChartView'; import WidgetContainer from '@components/WidgetContainer'; import WidgetHeaderMenu from '@components/WidgetHeaderMenu'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; @@ -30,6 +31,7 @@ import useInsightData from './useInsightData'; function InsightsSectionContent() { const styles = useThemeStyles(); + const {cardPaddingHorizontal, cardPaddingBottom} = useLayoutSpacing(); const {translate} = useLocalize(); const icons = useMemoizedLazyExpensifyIcons(['Expand']); const {shouldUseNarrowLayout} = useResponsiveLayout(); @@ -89,7 +91,7 @@ function InsightsSectionContent() { {state === INSIGHTS_CHART_STATE.EMPTY && } {state === INSIGHTS_CHART_STATE.ERROR && } {(state === INSIGHTS_CHART_STATE.LOADING || state === INSIGHTS_CHART_STATE.READY) && ( - + { const textX = RECEIPT_WIDTH + COLUMN_GAP; @@ -118,7 +118,7 @@ function RecentlyAddedPlaceholder({shouldShowSkeleton}: RecentlyAddedPlaceholder return ( {/* Measured without padding: the padded parent's width includes the inset, which pushes the right-aligned bars off the svg. */} openExpense(expense)} shouldShowSeparator={index < transactions.length - 1} shouldShowReceiptPreview={isFocused} - rowStyle={shouldUseNarrowLayout ? styles.ph5 : styles.ph8} + rowStyle={cardPaddingHorizontal} /> )) ) : ( diff --git a/src/pages/home/UpcomingTravelSection/UpcomingTravelItem.tsx b/src/pages/home/UpcomingTravelSection/UpcomingTravelItem.tsx index 707b503bac41..7e18315f7802 100644 --- a/src/pages/home/UpcomingTravelSection/UpcomingTravelItem.tsx +++ b/src/pages/home/UpcomingTravelSection/UpcomingTravelItem.tsx @@ -1,9 +1,9 @@ import Icon from '@components/Icon'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -84,7 +84,7 @@ function UpcomingTravelItem({reservation: upcomingReservation}: UpcomingTravelIt const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {cardPaddingHorizontal} = useLayoutSpacing(); const expensifyIcons = useMemoizedLazyExpensifyIcons([ 'Plane', 'PlaneCircleSlash', @@ -127,7 +127,7 @@ function UpcomingTravelItem({reservation: upcomingReservation}: UpcomingTravelIt /> } - wrapperStyle={[styles.alignItemsCenter, shouldUseNarrowLayout ? styles.ph5 : styles.ph8]} + wrapperStyle={[styles.alignItemsCenter, cardPaddingHorizontal]} hasSubMenuItems viewMode={CONST.OPTION_MODE.COMPACT} rightIconWrapperStyle={styles.pl2} diff --git a/src/pages/home/YourSpendSection/index.tsx b/src/pages/home/YourSpendSection/index.tsx index 61cb5fbb2882..de9fc34c4e1f 100644 --- a/src/pages/home/YourSpendSection/index.tsx +++ b/src/pages/home/YourSpendSection/index.tsx @@ -1,5 +1,6 @@ import WidgetContainer from '@components/WidgetContainer'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -25,6 +26,7 @@ function YourSpendSection() { const {translate} = useLocalize(); const styles = useThemeStyles(); const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {cardPaddingHorizontal} = useLayoutSpacing(); const icons = useMemoizedLazyExpensifyIcons(['ThumbsUpHourglass', 'MoneyBag']); const [isExpanded, setIsExpanded] = useState(false); @@ -42,7 +44,7 @@ function YourSpendSection() { return null; } - const wrapperStyle = [styles.alignItemsCenter, shouldUseNarrowLayout ? styles.ph5 : styles.ph8]; + const wrapperStyle = [styles.alignItemsCenter, cardPaddingHorizontal]; const visibleSummaryRowsCount = (isApprovalRowVisible ? 1 : 0) + (isPaymentRowVisible ? 1 : 0); const cardLimit = Math.max(0, CONST.HOME.SECTION_VISIBLE_LIMIT - visibleSummaryRowsCount); const hiddenCount = Math.max(0, cardRows.length - cardLimit); diff --git a/src/pages/home/common/widgetSkeletonRow.tsx b/src/pages/home/common/widgetSkeletonRow.tsx index 4cc3b6bce406..e7a282659b3a 100644 --- a/src/pages/home/common/widgetSkeletonRow.tsx +++ b/src/pages/home/common/widgetSkeletonRow.tsx @@ -1,6 +1,6 @@ import SkeletonRect from '@components/SkeletonRect'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; @@ -20,12 +20,12 @@ const BUTTON_BORDER_RADIUS = BUTTON_HEIGHT / 2; // Row geometry read off the styles the real rows use (see BaseWidgetItem). function useWidgetSkeletonRowGeometry() { const styles = useThemeStyles(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {values} = useLayoutSpacing(); return { iconTextGap: styles.gap3.gap, rowHeight: ICON_SLOT_SIZE + styles.pv3.paddingVertical * 2, - horizontalPadding: shouldUseNarrowLayout ? styles.ph5.paddingHorizontal : styles.ph8.paddingHorizontal, + horizontalPadding: values.cardPadding, }; } diff --git a/src/pages/inbox/sidebar/InboxTabSelector.tsx b/src/pages/inbox/sidebar/InboxTabSelector.tsx index 0e027bab7161..16e7fd8abe84 100644 --- a/src/pages/inbox/sidebar/InboxTabSelector.tsx +++ b/src/pages/inbox/sidebar/InboxTabSelector.tsx @@ -7,6 +7,7 @@ import TabSelectorContextProvider from '@components/TabSelector/TabSelectorConte import type {TabSelectorBaseItem} from '@components/TabSelector/types'; import useConfirmModal from '@hooks/useConfirmModal'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; @@ -45,6 +46,7 @@ function InboxTabSelector() { const {shouldUseNarrowLayout} = useResponsiveLayout(); const isScreenFocused = useIsSidebarRouteActive(NAVIGATORS.REPORTS_SPLIT_NAVIGATOR, shouldUseNarrowLayout); const styles = useThemeStyles(); + const {pageGutter} = useLayoutSpacing(); const {activeTab, inboxTabCounts, hasStaleUnreadReport} = useSidebarOrderedReportsState(); const {setActiveTab, getReportIDsForTab} = useSidebarOrderedReportsActions(); const [reportNameValuePairs] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS, {selector: reportNameValuePairsArchivedSelector}); @@ -167,6 +169,7 @@ function InboxTabSelector() { { diff --git a/src/pages/settings/Agents/AgentsPage.tsx b/src/pages/settings/Agents/AgentsPage.tsx index b46fb66312e2..deb62181cbee 100644 --- a/src/pages/settings/Agents/AgentsPage.tsx +++ b/src/pages/settings/Agents/AgentsPage.tsx @@ -15,6 +15,7 @@ import useChatWithAgent from '@hooks/useChatWithAgent'; import useCleanupSelectedOptions from '@hooks/useCleanupSelectedOptions'; import useConfirmModal from '@hooks/useConfirmModal'; import useDocumentTitle from '@hooks/useDocumentTitle'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; @@ -47,6 +48,7 @@ import {View} from 'react-native'; function AgentsPage() { const {translate} = useLocalize(); const styles = useThemeStyles(); + const {pageGutter} = useLayoutSpacing(); const {isOffline} = useNetwork(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const shouldDisplayButtonsInSeparateLine = useShouldDisplayButtonsInSeparateLine(); @@ -232,7 +234,7 @@ function AgentsPage() { const agentsTableHeaderComponent = ( <> - {shouldDisplayButtonsInSeparateLine && {headerButtons}} + {shouldDisplayButtonsInSeparateLine && {headerButtons}} {hasAgents && ( diff --git a/src/pages/settings/Rules/ExpenseRulesPage.tsx b/src/pages/settings/Rules/ExpenseRulesPage.tsx index 61ca570d12e6..26319671de3b 100644 --- a/src/pages/settings/Rules/ExpenseRulesPage.tsx +++ b/src/pages/settings/Rules/ExpenseRulesPage.tsx @@ -11,6 +11,7 @@ import Text from '@components/Text'; import useConfirmModal from '@hooks/useConfirmModal'; import useDocumentTitle from '@hooks/useDocumentTitle'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; @@ -46,6 +47,7 @@ function ExpenseRulesPage() { const styles = useThemeStyles(); const {isOffline} = useNetwork(); const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {pageGutter} = useLayoutSpacing(); const isMobileSelectionModeEnabled = useMobileSelectionMode(); const icons = useMemoizedLazyExpensifyIcons(['Pencil', 'Plus', 'Trashcan']); const [expenseRules = getEmptyArray(), expenseRulesResult] = useOnyx(ONYXKEYS.NVP_EXPENSE_RULES); @@ -229,7 +231,7 @@ function ExpenseRulesPage() { > {!shouldDisplayButtonsInSeparateLine && hasRules && headerButton} - {shouldDisplayButtonsInSeparateLine && hasRules && {headerButton}} + {shouldDisplayButtonsInSeparateLine && hasRules && {headerButton}} {!hasRules && expenseRulesSubtitle} diff --git a/src/pages/settings/Subscription/SubscriptionPlan/SubscriptionPlanCard.tsx b/src/pages/settings/Subscription/SubscriptionPlan/SubscriptionPlanCard.tsx index 7dda9eab54c9..9533fa985e7d 100644 --- a/src/pages/settings/Subscription/SubscriptionPlan/SubscriptionPlanCard.tsx +++ b/src/pages/settings/Subscription/SubscriptionPlan/SubscriptionPlanCard.tsx @@ -4,6 +4,7 @@ import RadioButton from '@components/RadioButton'; import Text from '@components/Text'; import useHasTeam2025Pricing from '@hooks/useHasTeam2025Pricing'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import usePreferredCurrency from '@hooks/usePreferredCurrency'; @@ -43,6 +44,7 @@ function SubscriptionPlanCard({subscriptionPlan, isFromComparisonModal = false, const theme = useTheme(); const {translate} = useLocalize(); const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {cardPadding, cardPaddingHorizontal} = useLayoutSpacing(); const currentSubscriptionPlan = useSubscriptionPlan(); const privateSubscription = usePrivateSubscription(); const preferredCurrency = usePreferredCurrency(); @@ -115,12 +117,12 @@ function SubscriptionPlanCard({subscriptionPlan, isFromComparisonModal = false, return ( {!privateSubscription ? ( - + ) : ( <> - + diff --git a/src/pages/settings/Wallet/InternationalDepositAccount/subPages/AccountFlowEntryPoint.tsx b/src/pages/settings/Wallet/InternationalDepositAccount/subPages/AccountFlowEntryPoint.tsx index c6cb638f5aa0..9fd52d29ea49 100644 --- a/src/pages/settings/Wallet/InternationalDepositAccount/subPages/AccountFlowEntryPoint.tsx +++ b/src/pages/settings/Wallet/InternationalDepositAccount/subPages/AccountFlowEntryPoint.tsx @@ -9,10 +9,10 @@ import Section from '@components/Section'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -41,7 +41,7 @@ function AccountFlowEntryPoint({policyName = '', onBackButtonPress}: AccountFlow const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {cardEdgeToEdge, cardMenuItemInset} = useLayoutSpacing(); const expensifyIcons = useMemoizedLazyExpensifyIcons(['Bank', 'Connect', 'Lightbulb', 'Lock']); const [isPlaidDisabled] = useOnyx(ONYXKEYS.IS_PLAID_DISABLED); @@ -115,18 +115,20 @@ function AccountFlowEntryPoint({policyName = '', onBackButtonPress}: AccountFlow {translate('workspace.bankAccount.connectBankAccountNote')} - - - + + + + +
diff --git a/src/pages/settings/Wallet/PaymentMethodListItem.tsx b/src/pages/settings/Wallet/PaymentMethodListItem.tsx index 71ee21078eff..1e4e354d2acb 100644 --- a/src/pages/settings/Wallet/PaymentMethodListItem.tsx +++ b/src/pages/settings/Wallet/PaymentMethodListItem.tsx @@ -12,9 +12,9 @@ import type RenderHTML from '@components/RenderHTML'; import Text from '@components/Text'; import ThreeDotsMenu from '@components/ThreeDotsMenu'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -148,7 +148,7 @@ function PaymentMethodListItem({item, shouldShowDefaultBadge, threeDotsMenuItems const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {cardPaddingHorizontal, cardPaddingLeft} = useLayoutSpacing(); const threeDotsMenuRef = useRef<{hidePopoverMenu: () => void; isPopupMenuVisible: boolean; onThreeDotsPress: () => void}>(null); const isInLockedState = isBusinessBankAccountLocked(item); @@ -322,7 +322,7 @@ function PaymentMethodListItem({item, shouldShowDefaultBadge, threeDotsMenuItems menuItem )} {!!item.shouldShowMissingPersonalDetailsAction && !!item.cardID && ( - + )} {isChaseAccountConnectedViaPlaid && ( - + openExternalLink(CONST.CHASE_ACCOUNT_NUMBER_HELP_URL)} style={[styles.flexRow, styles.alignItemsCenter, styles.alignSelfStart]} diff --git a/src/pages/settings/Wallet/WalletPage/index.tsx b/src/pages/settings/Wallet/WalletPage/index.tsx index 739ed0c9c531..96eacbe608dc 100644 --- a/src/pages/settings/Wallet/WalletPage/index.tsx +++ b/src/pages/settings/Wallet/WalletPage/index.tsx @@ -23,6 +23,7 @@ import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails' import useDelegateAccountID from '@hooks/useDelegateAccountID'; import useDocumentTitle from '@hooks/useDocumentTitle'; import {useIsAppLoadPending} from '@hooks/useInFlightRequests'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; @@ -125,6 +126,7 @@ function WalletPage() { const StyleUtils = useStyleUtils(); const network = useNetwork(); const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {cardPaddingHorizontal, cardEdgeToEdge} = useLayoutSpacing(); const {paymentMethod, setPaymentMethod, resetSelectedPaymentMethodData} = usePaymentMethodState(); const {showConfirmModal} = useConfirmModal(); const [shouldShowLoadingSpinner, setShouldShowLoadingSpinner] = useState(false); @@ -729,8 +731,8 @@ function WalletPage() { onPress={onBankAccountRowPressed} onAddBankAccountPress={addBankAccountPressed} onThreeDotsMenuPress={paymentMethodPressed} - style={[styles.mt5, [shouldUseNarrowLayout ? styles.mhn5 : styles.mhn8]]} - listItemStyle={shouldUseNarrowLayout ? styles.ph5 : styles.ph8} + style={[styles.mt5, cardEdgeToEdge]} + listItemStyle={cardPaddingHorizontal} shouldShowBankAccountSections shouldShowConnectionStatus threeDotsMenuItems={threeDotMenuItems} @@ -750,8 +752,8 @@ function WalletPage() { shouldShowAssignedCards onPress={assignedCardPressed} threeDotsMenuItems={cardThreeDotsMenuItems} - style={[styles.mt5, [shouldUseNarrowLayout ? styles.mhn5 : styles.mhn8]]} - listItemStyle={shouldUseNarrowLayout ? styles.ph5 : styles.ph8} + style={[styles.mt5, cardEdgeToEdge]} + listItemStyle={cardPaddingHorizontal} shouldShowConnectionStatus /> ) : ( - + {translate('workspace.bankAccount.chooseAnExisting')} { @@ -889,7 +891,7 @@ function WorkspaceMembersPage({personalDetails, route, policy}: WorkspaceMembers > {() => ( <> - {shouldDisplayButtonsInSeparateLine && {getHeaderButtons()}} + {shouldDisplayButtonsInSeparateLine && {getHeaderButtons()}} - {shouldDisplayButtonsInSeparateLine && {headerButtons}} + {shouldDisplayButtonsInSeparateLine && {headerButtons}}
{shouldShowRulesDocumentSubSection && ( {!shouldDisplayButtonsInSeparateLine && getHeaderButtons()} - {shouldDisplayButtonsInSeparateLine && !!getHeaderButtons() && {getHeaderButtons()}} + {shouldDisplayButtonsInSeparateLine && !!getHeaderButtons() && {getHeaderButtons()}} {(!hasVisibleCategories || isLoading) && headerContent} diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx index b9995ac400b4..a1e464a8e0c4 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx @@ -11,6 +11,7 @@ import Text from '@components/Text'; import useCleanupSelectedOptions from '@hooks/useCleanupSelectedOptions'; import useConfirmModal from '@hooks/useConfirmModal'; import useFilteredSelection from '@hooks/useFilteredSelection'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; @@ -66,6 +67,7 @@ function PolicyDistanceRatesPage({ const icons = useMemoizedLazyExpensifyIcons(['Checkmark', 'Close', 'Gear', 'Plus', 'Trashcan']); const {shouldUseNarrowLayout, isInLandscapeMode} = useResponsiveLayout(); const styles = useThemeStyles(); + const {pageGutter} = useLayoutSpacing(); const {translate, toLocaleDigit} = useLocalize(); const {showConfirmModal} = useConfirmModal(); const policy = usePolicy(policyID); @@ -499,7 +501,7 @@ function PolicyDistanceRatesPage({ > {!shouldDisplayButtonsInSeparateLine && headerButtons} - {shouldDisplayButtonsInSeparateLine && !!headerButtons && {headerButtons}} + {shouldDisplayButtonsInSeparateLine && !!headerButtons && {headerButtons}} {isLoading && ( - {shouldDisplayButtonsInSeparateLine && {headerButtons}} + {shouldDisplayButtonsInSeparateLine && {headerButtons}} {subtitle} diff --git a/src/pages/workspace/fields/WorkspaceFieldsSection.tsx b/src/pages/workspace/fields/WorkspaceFieldsSection.tsx index 8b0a80da9d05..153549291517 100644 --- a/src/pages/workspace/fields/WorkspaceFieldsSection.tsx +++ b/src/pages/workspace/fields/WorkspaceFieldsSection.tsx @@ -7,12 +7,12 @@ import Section from '@components/Section'; import Text from '@components/Text'; import useConfirmModal from '@hooks/useConfirmModal'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; import usePolicyFeatureWriteAccess from '@hooks/usePolicyFeatureWriteAccess'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import {isConnectionInProgress, isConnectionUnverified} from '@libs/actions/connections'; @@ -107,7 +107,7 @@ function WorkspaceFieldsSection({ const styles = useThemeStyles(); const {translate, localeCompare} = useLocalize(); const icons = useMemoizedLazyExpensifyIcons(['Plus']); - const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {cardPadding, cardPaddingHorizontal, cardEdgeToEdge} = useLayoutSpacing(); const {showConfirmModal} = useConfirmModal(); const [connectionSyncProgress] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${policyID}`); const isSyncInProgress = isConnectionInProgress(connectionSyncProgress, policy); @@ -166,7 +166,7 @@ function WorkspaceFieldsSection({ ({item}: ListRenderItemInfo) => ( navigateToFieldSettings(item)} description={item.text} disabled={item.isDisabled} @@ -177,7 +177,7 @@ function WorkspaceFieldsSection({ /> ), - [canWrite, navigateToFieldSettings, shouldUseNarrowLayout, styles.ph5, styles.ph8, styles.popoverMenuText, styles.textStrong], + [canWrite, navigateToFieldSettings, cardPaddingHorizontal, styles.popoverMenuText, styles.textStrong], ); const headerText = @@ -217,7 +217,7 @@ function WorkspaceFieldsSection({ return (
- + {!isLoading && (
diff --git a/src/pages/workspace/invoices/WorkspaceInvoicingDetailsSection.tsx b/src/pages/workspace/invoices/WorkspaceInvoicingDetailsSection.tsx index 511406f695f1..f282ddf5dd93 100644 --- a/src/pages/workspace/invoices/WorkspaceInvoicingDetailsSection.tsx +++ b/src/pages/workspace/invoices/WorkspaceInvoicingDetailsSection.tsx @@ -1,9 +1,9 @@ import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import Section from '@components/Section'; +import useLayoutSpacing from '@hooks/useLayoutSpacing'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@navigation/Navigation'; @@ -12,7 +12,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import React, {useMemo} from 'react'; +import React from 'react'; type WorkspaceInvoicingDetailsSectionProps = { policyID: string; @@ -24,16 +24,16 @@ type WorkspaceInvoicingDetailsSectionProps = { function WorkspaceInvoicingDetailsSection({policyID, canWriteMoreFeatures}: WorkspaceInvoicingDetailsSectionProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {cardPaddingHorizontal, cardPaddingTop} = useLayoutSpacing(); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); - const horizontalPadding = useMemo(() => (shouldUseNarrowLayout ? styles.ph5 : styles.ph8), [shouldUseNarrowLayout, styles]); + const horizontalPadding = cardPaddingHorizontal; return (
{!shouldDisplayButtonsInSeparateLine && headerButtons} - {!!headerButtons && shouldDisplayButtonsInSeparateLine && {headerButtons}} + {!!headerButtons && shouldDisplayButtonsInSeparateLine && {headerButtons}} {!hasVisibleSubRates && subtitleContent} +