From 517bf4dc23c4dd8a455ef8e8f2dd582b1fb75877 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Sat, 27 Jul 2024 12:19:28 +0200 Subject: [PATCH 01/20] Update readme --- package.json | 1 + src/languages/en.ts | 12 + src/languages/es.ts | 12 + src/libs/LocaleDigitUtils.ts | 12 +- src/types/modules/react-native.d.ts | 359 +--------------------------- 5 files changed, 35 insertions(+), 361 deletions(-) diff --git a/package.json b/package.json index 5da6a291f261..6ec59d2e1fd3 100644 --- a/package.json +++ b/package.json @@ -238,6 +238,7 @@ "@types/node": "^20.11.5", "@types/pusher-js": "^5.1.0", "@types/react": "18.2.45", + "@types/react-native-web": "0.73.0", "@types/react-beautiful-dnd": "^13.1.4", "@types/react-collapse": "^5.0.1", "@types/react-dom": "^18.2.4", diff --git a/src/languages/en.ts b/src/languages/en.ts index 1cb58198425f..9659ca37234a 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1266,6 +1266,18 @@ export default { two: 'nd', few: 'rd', other: 'th', + /* eslint-disable @typescript-eslint/naming-convention */ + '1': 'First', + '2': 'Second', + '3': 'Third', + '4': 'Fourth', + '5': 'Fifth', + '6': 'Sixth', + '7': 'Seventh', + '8': 'Eighth', + '9': 'Ninth', + '10': 'Tenth', + /* eslint-enable @typescript-eslint/naming-convention */ }, }, }, diff --git a/src/languages/es.ts b/src/languages/es.ts index 92936b6371ee..9b6c2b718f53 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1275,6 +1275,18 @@ export default { two: '.º', few: '.º', other: '.º', + /* eslint-disable @typescript-eslint/naming-convention */ + '1': 'Primero', + '2': 'Segundo', + '3': 'Tercero', + '4': 'Cuarto', + '5': 'Quinto', + '6': 'Sexto', + '7': 'Séptimo', + '8': 'Octavo', + '9': 'Noveno', + '10': 'Décimo', + /* eslint-enable @typescript-eslint/naming-convention */ }, }, }, diff --git a/src/libs/LocaleDigitUtils.ts b/src/libs/LocaleDigitUtils.ts index a024276819c1..2bb57922a6c9 100644 --- a/src/libs/LocaleDigitUtils.ts +++ b/src/libs/LocaleDigitUtils.ts @@ -1,4 +1,4 @@ -import type {ValueOf} from 'type-fest'; +import type {Integer, ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import * as Localize from './Localize'; @@ -74,9 +74,9 @@ function fromLocaleDigit(locale: Locale, localeDigit: string): string { /** * Formats a number into its localized ordinal representation i.e 1st, 2nd etc */ -function toLocaleOrdinal(locale: Locale, number: number): string { +function toLocaleOrdinal(locale: Locale, number: number, returnWords = false): string { // Defaults to "other" suffix or "th" in English - let suffixKey = 'workflowsPage.frequencies.ordinals.other'; + let suffixKey: TranslationPaths = 'workflowsPage.frequencies.ordinals.other'; // Calculate last digit of the number to determine basic ordinality const lastDigit = number % 10; @@ -92,7 +92,11 @@ function toLocaleOrdinal(locale: Locale, number: number): string { suffixKey = 'workflowsPage.frequencies.ordinals.few'; } - const suffix = Localize.translate(locale, suffixKey as TranslationPaths); + if (returnWords && number >= 1 && number <= 10) { + suffixKey = `workflowsPage.frequencies.ordinals.${number}` as TranslationPaths; + } + + const suffix = Localize.translate(locale, suffixKey); return `${number}${suffix}`; } diff --git a/src/types/modules/react-native.d.ts b/src/types/modules/react-native.d.ts index 87628fded0cc..20f6e8ed425a 100644 --- a/src/types/modules/react-native.d.ts +++ b/src/types/modules/react-native.d.ts @@ -4,9 +4,8 @@ /* eslint-disable @typescript-eslint/consistent-type-definitions */ // eslint-disable-next-line no-restricted-imports -import type {CSSProperties, FocusEventHandler, KeyboardEventHandler, MouseEventHandler, PointerEventHandler, TouchEventHandler, UIEventHandler, WheelEventHandler} from 'react'; -import 'react-native'; -import type {GestureResponderEvent, LayoutRectangle, NativeSyntheticEvent, TargetedEvent} from 'react-native'; +import '@types/react-native-web'; +import type {TargetedEvent} from 'react-native'; import type {BootSplashModule} from '@libs/BootSplash/types'; import type {EnvironmentCheckerModule} from '@libs/Environment/betaChecker/types'; import type StartupTimer from '@libs/StartupTimer/types'; @@ -22,360 +21,6 @@ type RNTextInputResetModule = { }; declare module 'react-native' { - // <------ REACT NATIVE WEB (0.19.12) ------> - // Extracted from react-native-web, packages/react-native-web/src/exports/View/types.js - type idRef = string; - type idRefList = idRef | idRef[]; - - // https://necolas.github.io/react-native-web/docs/accessibility/#accessibility-props-api - // Extracted from react-native-web, packages/react-native-web/src/exports/View/types.js - interface AccessibilityProps { - 'aria-activedescendant'?: idRef; - 'aria-atomic'?: boolean; - 'aria-autocomplete'?: 'none' | 'list' | 'inline' | 'both'; - 'aria-busy'?: boolean; - 'aria-checked'?: boolean | 'mixed'; - 'aria-colcount'?: number; - 'aria-colindex'?: number; - 'aria-colspan'?: number; - 'aria-controls'?: idRef; - 'aria-current'?: boolean | 'page' | 'step' | 'location' | 'date' | 'time'; - 'aria-describedby'?: idRef; - 'aria-details'?: idRef; - 'aria-disabled'?: boolean; - 'aria-errormessage'?: idRef; - 'aria-expanded'?: boolean; - 'aria-flowto'?: idRef; - 'aria-haspopup'?: 'dialog' | 'grid' | 'listbox' | 'menu' | 'tree' | false; - 'aria-hidden'?: boolean; - 'aria-invalid'?: boolean; - 'aria-keyshortcuts'?: string; - 'aria-label'?: string; - 'aria-labelledby'?: idRef; - 'aria-level'?: number; - 'aria-live'?: 'assertive' | 'off' | 'polite'; - 'aria-modal'?: boolean; - 'aria-multiline'?: boolean; - 'aria-multiselectable'?: boolean; - 'aria-orientation'?: 'horizontal' | 'vertical'; - 'aria-owns'?: idRef; - 'aria-placeholder'?: string; - 'aria-posinset'?: number; - 'aria-pressed'?: boolean | 'mixed'; - 'aria-readonly'?: boolean; - 'aria-required'?: boolean; - 'aria-roledescription'?: string; - 'aria-rowcount'?: number; - 'aria-rowindex'?: number; - 'aria-rowspan'?: number; - 'aria-selected'?: boolean; - 'aria-setsize'?: number; - 'aria-sort'?: 'ascending' | 'descending' | 'none' | 'other'; - 'aria-valuemax'?: number; - 'aria-valuemin'?: number; - 'aria-valuenow'?: number; - 'aria-valuetext'?: string; - - // @deprecated - accessibilityActiveDescendant?: idRef; - accessibilityAtomic?: boolean; - accessibilityAutoComplete?: 'none' | 'list' | 'inline' | 'both'; - accessibilityBusy?: boolean; - accessibilityChecked?: boolean | 'mixed'; - accessibilityColumnCount?: number; - accessibilityColumnIndex?: number; - accessibilityColumnSpan?: number; - accessibilityControls?: idRefList; - accessibilityCurrent?: boolean | 'page' | 'step' | 'location' | 'date' | 'time'; - accessibilityDescribedBy?: idRefList; - accessibilityDetails?: idRef; - accessibilityDisabled?: boolean; - accessibilityErrorMessage?: idRef; - accessibilityExpanded?: boolean; - accessibilityFlowTo?: idRefList; - accessibilityHasPopup?: 'dialog' | 'grid' | 'listbox' | 'menu' | 'tree' | false; - accessibilityHidden?: boolean; - accessibilityInvalid?: boolean; - accessibilityKeyShortcuts?: string[]; - accessibilityLabel?: string; - accessibilityLabelledBy?: idRef; - accessibilityLevel?: number; - accessibilityLiveRegion?: 'assertive' | 'none' | 'polite'; - accessibilityModal?: boolean; - accessibilityMultiline?: boolean; - accessibilityMultiSelectable?: boolean; - accessibilityOrientation?: 'horizontal' | 'vertical'; - accessibilityOwns?: idRefList; - accessibilityPlaceholder?: string; - accessibilityPosInSet?: number; - accessibilityPressed?: boolean | 'mixed'; - accessibilityReadOnly?: boolean; - accessibilityRequired?: boolean; - accessibilityRoleDescription?: string; - accessibilityRowCount?: number; - accessibilityRowIndex?: number; - accessibilityRowSpan?: number; - accessibilitySelected?: boolean; - accessibilitySetSize?: number; - accessibilitySort?: 'ascending' | 'descending' | 'none' | 'other'; - accessibilityValueMax?: number; - accessibilityValueMin?: number; - accessibilityValueNow?: number; - accessibilityValueText?: string; - } - - // https://necolas.github.io/react-native-web/docs/interactions/#pointerevent-props-api - // Extracted properties from react-native-web, packages/react-native-web/src/exports/View/types.js and packages/react-native-web/src/modules/forwardedProps/index.js - // Extracted types from @types/react, index.d.ts - interface ClickProps { - onClick?: MouseEventHandler; - onAuxClick?: MouseEventHandler; - onContextMenu?: MouseEventHandler; - onGotPointerCapture?: PointerEventHandler; - onLostPointerCapture?: PointerEventHandler; - onPointerCancel?: PointerEventHandler; - onPointerDown?: PointerEventHandler; - onPointerEnter?: PointerEventHandler; - onPointerMove?: PointerEventHandler; - onPointerLeave?: PointerEventHandler; - onPointerOut?: PointerEventHandler; - onPointerOver?: PointerEventHandler; - onPointerUp?: PointerEventHandler; - onScroll?: UIEventHandler; - onWheel?: WheelEventHandler; - } - - // https://necolas.github.io/react-native-web/docs/interactions/#focusevent-props-api - // Extracted properties from react-native-web, packages/react-native-web/src/exports/View/types.js and packages/react-native-web/src/modules/forwardedProps/index.js - // Extracted types from @types/react, index.d.ts - interface FocusProps { - onBlur?: FocusEventHandler; - onFocus?: FocusEventHandler; - } - - // https://necolas.github.io/react-native-web/docs/interactions/#keyboardevent-props-api - // Extracted properties from react-native-web, packages/react-native-web/src/exports/View/types.js and packages/react-native-web/src/modules/forwardedProps/index.js - // Extracted types from @types/react, index.d.ts - interface KeyboardProps { - onKeyDown?: KeyboardEventHandler; - onKeyDownCapture?: KeyboardEventHandler; - onKeyUp?: KeyboardEventHandler; - onKeyUpCapture?: KeyboardEventHandler; - } - - // Extracted properties from react-native-web, packages/react-native-web/src/exports/View/types.js and packages/react-native-web/src/modules/forwardedProps/index.js - // Extracted types from @types/react, index.d.ts - interface MouseProps { - onMouseDown?: MouseEventHandler; - onMouseEnter?: MouseEventHandler; - onMouseLeave?: MouseEventHandler; - onMouseMove?: MouseEventHandler; - onMouseOver?: MouseEventHandler; - onMouseOut?: MouseEventHandler; - onMouseUp?: MouseEventHandler; - } - - // Extracted properties from react-native-web, packages/react-native-web/src/exports/View/types.js and packages/react-native-web/src/modules/forwardedProps/index.js - // Extracted types from @types/react, index.d.ts - interface TouchProps { - onTouchCancel?: TouchEventHandler; - onTouchCancelCapture?: TouchEventHandler; - onTouchEnd?: TouchEventHandler; - onTouchEndCapture?: TouchEventHandler; - onTouchMove?: TouchEventHandler; - onTouchMoveCapture?: TouchEventHandler; - onTouchStart?: TouchEventHandler; - onTouchStartCapture?: TouchEventHandler; - } - - // https://necolas.github.io/react-native-web/docs/interactions/#responderevent-props-api - // Extracted from react-native-web, packages/react-native-web/src/modules/useResponderEvents/ResponderTouchHistoryStore.js - type TouchRecord = { - currentPageX: number; - currentPageY: number; - currentTimeStamp: number; - previousPageX: number; - previousPageY: number; - previousTimeStamp: number; - startPageX: number; - startPageY: number; - startTimeStamp: number; - touchActive: boolean; - }; - - // https://necolas.github.io/react-native-web/docs/interactions/#responderevent-props-api - // Extracted from react-native-web, packages/react-native-web/src/modules/useResponderEvents/ResponderTouchHistoryStore.js - type TouchHistory = Readonly<{ - indexOfSingleActiveTouch: number; - mostRecentTimeStamp: number; - numberActiveTouches: number; - touchBank: TouchRecord[]; - }>; - - // https://necolas.github.io/react-native-web/docs/interactions/#responderevent-props-api - // Extracted from react-native-web, packages/react-native-web/src/modules/useResponderEvents/createResponderEvent.js - type ResponderEvent = { - bubbles: boolean; - cancelable: boolean; - currentTarget?: unknown; // changed from "any" to "unknown" - defaultPrevented?: boolean; - dispatchConfig: { - registrationName?: string; - phasedRegistrationNames?: { - bubbled: string; - captured: string; - }; - }; - eventPhase?: number; - isDefaultPrevented: () => boolean; - isPropagationStopped: () => boolean; - isTrusted?: boolean; - preventDefault: () => void; - stopPropagation: () => void; - nativeEvent: TouchEvent; - persist: () => void; - target?: unknown; // changed from "any" to "unknown" - timeStamp: number; - touchHistory: TouchHistory; - }; - - // https://necolas.github.io/react-native-web/docs/interactions/#responderevent-props-api - // Extracted from react-native-web, packages/react-native-web/src/modules/useResponderEvents/ResponderSystem.js - interface ResponderProps { - // Direct responder events dispatched directly to responder. Do not bubble. - onResponderEnd?: (e: ResponderEvent) => void; - onResponderGrant?: (e: ResponderEvent) => void | boolean; - onResponderMove?: (e: ResponderEvent) => void; - onResponderRelease?: (e: ResponderEvent) => void; - onResponderReject?: (e: ResponderEvent) => void; - onResponderStart?: (e: ResponderEvent) => void; - onResponderTerminate?: (e: ResponderEvent) => void; - onResponderTerminationRequest?: (e: ResponderEvent) => boolean; - - // On pointer down, should this element become the responder? - onStartShouldSetResponder?: (e: ResponderEvent) => boolean; - onStartShouldSetResponderCapture?: (e: ResponderEvent) => boolean; - - // On pointer move, should this element become the responder? - onMoveShouldSetResponder?: (e: ResponderEvent) => boolean; - onMoveShouldSetResponderCapture?: (e: ResponderEvent) => boolean; - - // On scroll, should this element become the responder? Do no bubble - onScrollShouldSetResponder?: (e: ResponderEvent) => boolean; - onScrollShouldSetResponderCapture?: (e: ResponderEvent) => boolean; - - // On text selection change, should this element become the responder? - onSelectionChangeShouldSetResponder?: (e: ResponderEvent) => boolean; - onSelectionChangeShouldSetResponderCapture?: (e: ResponderEvent) => boolean; - } - - // @ts-expect-error We override this type in order to provide "target" to onLayout event. - type LayoutChangeEvent = NativeSyntheticEvent<{layout: LayoutRectangle; target?: unknown}>; - - interface EventProps extends ClickProps, FocusProps, KeyboardProps, MouseProps, TouchProps, ResponderProps {} - - /** - * Shared props - * Extracted from react-native-web, packages/react-native-web/src/exports/View/types.js - */ - interface WebSharedProps extends AccessibilityProps, EventProps { - dataSet?: Record; - href?: string; - hrefAttrs?: { - download?: boolean; - rel?: string; - target?: string; - }; - tabIndex?: 0 | -1; - lang?: string; - } - - /** - * View - * Extracted from react-native-web, packages/react-native-web/src/exports/View/types.js - */ - interface WebViewProps extends WebSharedProps { - dir?: 'ltr' | 'rtl'; - } - interface ViewProps extends WebViewProps {} - - /** - * Text - * Extracted from react-native-web, packages/react-native-web/src/exports/Text/types.js - */ - interface WebTextProps extends WebSharedProps { - dir?: 'auto' | 'ltr' | 'rtl'; - } - interface TextProps extends WebTextProps {} - - /** - * TextInput - * Extracted from react-native-web, packages/react-native-web/src/exports/TextInput/types.js - */ - interface WebTextInputProps extends WebSharedProps { - dir?: 'auto' | 'ltr' | 'rtl'; - disabled?: boolean; - } - interface TextInputProps extends WebTextInputProps {} - - /** - * Image - * Extracted from react-native-web, packages/react-native-web/src/exports/Image/types.js - */ - interface WebImageProps extends WebSharedProps { - dir?: 'ltr' | 'rtl'; - draggable?: boolean; - } - interface ImageProps extends WebImageProps {} - - /** - * ScrollView - * Extracted from react-native-web, packages/react-native-web/src/exports/ScrollView/ScrollViewBase.js - */ - interface WebScrollViewProps extends WebSharedProps {} - interface ScrollViewProps extends WebScrollViewProps {} - - /** - * Pressable - */ - // https://necolas.github.io/react-native-web/docs/pressable/#interactionstate - // Extracted from react-native-web, packages/react-native-web/src/exports/Pressable/index.js - interface WebPressableStateCallbackType { - readonly focused: boolean; - readonly hovered: boolean; - readonly pressed: boolean; - } - interface PressableStateCallbackType extends WebPressableStateCallbackType {} - - // Extracted from react-native-web, packages/react-native-web/src/exports/Pressable/index.js - interface WebPressableProps extends WebSharedProps { - /** Duration (in milliseconds) from `onPressStart` is called after pointerdown. */ - delayPressIn?: number; - /** Duration (in milliseconds) from `onPressEnd` is called after pointerup. */ - delayPressOut?: number; - /** Called when a touch is moving, after `onPressIn`. */ - onPressMove?: (event: GestureResponderEvent) => void; - } - interface PressableProps extends WebPressableProps {} - - /** - * Styles - */ - // We extend CSSProperties (alias to "csstype" library) which provides all CSS style properties for Web, - // but properties that are already defined on RN won't be overrided / augmented. - interface WebStyle extends CSSProperties { - // https://necolas.github.io/react-native-web/docs/styling/#non-standard-properties - // Exclusive to react-native-web, "pointerEvents" already included on RN - animationKeyframes?: string | Record; - writingDirection?: 'auto' | 'ltr' | 'rtl'; - enableBackground?: string; - } - - interface ViewStyle extends WebStyle {} - interface TextStyle extends WebStyle {} - interface ImageStyle extends WebStyle {} - // <------ REACT NATIVE WEB (0.19.12) ------> - interface TextInputFocusEventData extends TargetedEvent { text: string; eventCount: number; From 650eda9ece9774b08b9663277952b1a76c94aaa9 Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Tue, 30 Jul 2024 15:14:36 +0200 Subject: [PATCH 02/20] wip --- src/components/ApprovalWorkflowSection.tsx | 144 +++++++++++++++++++++ src/components/Temp.tsx | 8 ++ 2 files changed, 152 insertions(+) create mode 100644 src/components/ApprovalWorkflowSection.tsx create mode 100644 src/components/Temp.tsx diff --git a/src/components/ApprovalWorkflowSection.tsx b/src/components/ApprovalWorkflowSection.tsx new file mode 100644 index 000000000000..d988cbe8e813 --- /dev/null +++ b/src/components/ApprovalWorkflowSection.tsx @@ -0,0 +1,144 @@ +import React from 'react'; +import {View} from 'react-native'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import type ApprovalWorkflow from '@src/types/onyx/ApprovalWorkflow'; +// import type {Approver, Member} from '@src/types/onyx/ApprovalWorkflow'; +import Icon from './Icon'; +import * as Expensicons from './Icon/Expensicons'; +import MenuItem from './MenuItem'; +import PressableWithFeedback from './Pressable/PressableWithFeedback'; +import Text from './Text'; + +// MEMBER +// { +// email: string; +// avatar?: AvatarSource; +// displayName?: string; +// } + +// type Approver = { +// email: string; +// forwardsTo?: string; +// avatar?: AvatarSource; +// displayName?: string; +// isInMultipleWorkflows: boolean; +// isCircularReference?: boolean; +// }; + +// const mockApprover1 = { +// email: 'dx@gmail.com', +// forwardsTo: 'dx@gmail.com', +// avatar: undefined, +// displayName: 'dx', +// isInMultipleWorkflows: false, +// isCircularReference: true, +// }; + +// const mockApprover2 = { +// email: 'xd@gmail.com', +// forwardsTo: 'xd@gmail.com', +// avatar: undefined, +// displayName: 'xd', +// isInMultipleWorkflows: false, +// isCircularReference: true, +// }; + +// const mockMember1 = { +// email: 'xdd@gmail.com', +// avatar: undefined, +// displayName: 'xdd', +// }; + +// const mockMember2 = { +// email: 'ddx@gmail.com', +// avatar: undefined, +// displayName: 'ddx', +// }; + +// const mockWorkflow: ApprovalWorkflow = { +// members: [mockMember1, mockMember2], +// approvers: [mockApprover1, mockApprover2], +// isDefault: false, +// isBeingEdited: false, +// isLoading: false, +// }; + +// type ApprovalWorkflow = { +// members: Member[]; +// approvers: Approver[]; +// isDefault: boolean; +// isBeingEdited: boolean; +// isLoading?: boolean; +// }; + +function ApprovalWorkflowSection({approvalWorkflow}: {approvalWorkflow: ApprovalWorkflow}) { + const styles = useThemeStyles(); + const theme = useTheme(); + // const approvalWorkflow = approvalWorkflow1 ?? mockWorkflow; + + return ( + {}} + accessibilityLabel="WORKSHOP THIS" + > + + {approvalWorkflow.isDefault && ( + + + + This default workflow applies to all members, unless a more specific workflow exists. + + + )} + + m.displayName).join(', ')} + icon={Expensicons.Users} + iconHeight={20} + iconWidth={20} + iconFill={theme.icon} + interactive={false} + /> + + {approvalWorkflow.approvers.map((approver) => ( + + + + + ))} + + + + ); +} + +export default ApprovalWorkflowSection; diff --git a/src/components/Temp.tsx b/src/components/Temp.tsx new file mode 100644 index 000000000000..0991d6fc78b6 --- /dev/null +++ b/src/components/Temp.tsx @@ -0,0 +1,8 @@ +import React from 'react'; +import Text from './Text'; + +function Temp() { + return TEMP; +} + +export default Temp; From c91b34a6bbe7b4b15379078ae01a02a4058e1cea Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Wed, 31 Jul 2024 11:16:12 +0200 Subject: [PATCH 03/20] section ready, subsections not working --- assets/images/user-check.svg | 9 ++ src/components/ApprovalWorkflowSection.tsx | 86 +++---------------- src/components/Icon/Expensicons.ts | 2 + src/languages/en.ts | 2 + src/languages/es.ts | 2 + .../workflows/WorkspaceWorkflowsPage.tsx | 41 +++++---- 6 files changed, 47 insertions(+), 95 deletions(-) create mode 100644 assets/images/user-check.svg diff --git a/assets/images/user-check.svg b/assets/images/user-check.svg new file mode 100644 index 000000000000..e8dd4a3ef09c --- /dev/null +++ b/assets/images/user-check.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/components/ApprovalWorkflowSection.tsx b/src/components/ApprovalWorkflowSection.tsx index d988cbe8e813..bf6fb60cc919 100644 --- a/src/components/ApprovalWorkflowSection.tsx +++ b/src/components/ApprovalWorkflowSection.tsx @@ -1,112 +1,50 @@ import React from 'react'; import {View} from 'react-native'; +import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import type ApprovalWorkflow from '@src/types/onyx/ApprovalWorkflow'; -// import type {Approver, Member} from '@src/types/onyx/ApprovalWorkflow'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import MenuItem from './MenuItem'; import PressableWithFeedback from './Pressable/PressableWithFeedback'; import Text from './Text'; -// MEMBER -// { -// email: string; -// avatar?: AvatarSource; -// displayName?: string; -// } - -// type Approver = { -// email: string; -// forwardsTo?: string; -// avatar?: AvatarSource; -// displayName?: string; -// isInMultipleWorkflows: boolean; -// isCircularReference?: boolean; -// }; - -// const mockApprover1 = { -// email: 'dx@gmail.com', -// forwardsTo: 'dx@gmail.com', -// avatar: undefined, -// displayName: 'dx', -// isInMultipleWorkflows: false, -// isCircularReference: true, -// }; - -// const mockApprover2 = { -// email: 'xd@gmail.com', -// forwardsTo: 'xd@gmail.com', -// avatar: undefined, -// displayName: 'xd', -// isInMultipleWorkflows: false, -// isCircularReference: true, -// }; - -// const mockMember1 = { -// email: 'xdd@gmail.com', -// avatar: undefined, -// displayName: 'xdd', -// }; - -// const mockMember2 = { -// email: 'ddx@gmail.com', -// avatar: undefined, -// displayName: 'ddx', -// }; - -// const mockWorkflow: ApprovalWorkflow = { -// members: [mockMember1, mockMember2], -// approvers: [mockApprover1, mockApprover2], -// isDefault: false, -// isBeingEdited: false, -// isLoading: false, -// }; - -// type ApprovalWorkflow = { -// members: Member[]; -// approvers: Approver[]; -// isDefault: boolean; -// isBeingEdited: boolean; -// isLoading?: boolean; -// }; - function ApprovalWorkflowSection({approvalWorkflow}: {approvalWorkflow: ApprovalWorkflow}) { const styles = useThemeStyles(); const theme = useTheme(); - // const approvalWorkflow = approvalWorkflow1 ?? mockWorkflow; + const {translate} = useLocalize(); return ( {}} - accessibilityLabel="WORKSHOP THIS" + accessibilityLabel={translate('workflowsPage.addApprovalsTitle')} > {approvalWorkflow.isDefault && ( - + - This default workflow applies to all members, unless a more specific workflow exists. + {translate('workflowsPage.addApprovalTip')} )} - m.displayName).join(', ')} + description={approvalWorkflow.isDefault ? translate('workspace.common.everyone') : approvalWorkflow.members.map((m) => m.displayName).join(', ')} icon={Expensicons.Users} iconHeight={20} iconWidth={20} @@ -118,12 +56,12 @@ function ApprovalWorkflowSection({approvalWorkflow}: {approvalWorkflow: Approval PersonalDetailsUtils.getPersonalDetailByEmail(policy?.achAccount?.reimburser ?? '')?.displayName ?? policy?.achAccount?.reimburser, [policy?.achAccount?.reimburser], @@ -143,28 +148,22 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr }, subMenuItems: ( <> - Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_APPROVER.getRoute(route.params.policyID))} - shouldShowRightIcon - wrapperStyle={[styles.sectionMenuItemTopDescription, styles.mt3, styles.mbn3]} - brickRoadIndicator={hasApprovalError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} - /> {/* TODO: Functionality for this button will be added in a future PR (https://github.com/Expensify/App/issues/45954) */} {canUseAdvancedApproval && ( - + <> + {workflows.map((w) => ( + + ))} + + )} ), From dd28d1b200b2f66a80d0aafc90d9b73339043b57 Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Wed, 31 Jul 2024 11:20:42 +0200 Subject: [PATCH 04/20] useMemo and ts fix --- .../workspace/workflows/WorkspaceWorkflowsPage.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx index c70fbf4e6698..1a819b63623e 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx @@ -57,12 +57,14 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout(); const policyApproverEmail = policy?.approver; - const policyApproverName = useMemo(() => PersonalDetailsUtils.getPersonalDetailByEmail(policyApproverEmail ?? '')?.displayName ?? policyApproverEmail, [policyApproverEmail]); const canUseAdvancedApproval = Permissions.canUseWorkflowsAdvancedApproval(betas); const [isCurrencyModalOpen, setIsCurrencyModalOpen] = useState(false); const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); - const workflows = convertPolicyEmployeesToApprovalWorkflows({employees: policy?.employeeList ?? {}, defaultApprover: policyApproverEmail ?? '', personalDetails: personalDetails ?? {}}); + const workflows = useMemo( + () => convertPolicyEmployeesToApprovalWorkflows({employees: policy?.employeeList ?? {}, defaultApprover: policyApproverEmail ?? '', personalDetails: personalDetails ?? {}}), + [personalDetails, policy?.employeeList, policyApproverEmail], + ); const displayNameForAuthorizedPayer = useMemo( () => PersonalDetailsUtils.getPersonalDetailByEmail(policy?.achAccount?.reimburser ?? '')?.displayName ?? policy?.achAccount?.reimburser, @@ -260,9 +262,10 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr translate, preferredLocale, onPressAutoReportingFrequency, - policyApproverName, canUseAdvancedApproval, - theme, + workflows, + theme.success, + theme.spinner, isOffline, isPolicyAdmin, displayNameForAuthorizedPayer, From 27cebe19bde835f3d12c4d98d9cd2aa89c3d28c7 Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Wed, 31 Jul 2024 12:15:08 +0200 Subject: [PATCH 05/20] add navigation to approval edit --- src/components/ApprovalWorkflowSection.tsx | 12 +++++++++--- .../workflows/WorkspaceWorkflowsPage.tsx | 18 ++++++++++++++++-- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/components/ApprovalWorkflowSection.tsx b/src/components/ApprovalWorkflowSection.tsx index bf6fb60cc919..eb0d01754bcc 100644 --- a/src/components/ApprovalWorkflowSection.tsx +++ b/src/components/ApprovalWorkflowSection.tsx @@ -1,8 +1,10 @@ -import React from 'react'; +import React, {useCallback} from 'react'; import {View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import ROUTES from '@src/ROUTES'; import type ApprovalWorkflow from '@src/types/onyx/ApprovalWorkflow'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; @@ -10,16 +12,20 @@ import MenuItem from './MenuItem'; import PressableWithFeedback from './Pressable/PressableWithFeedback'; import Text from './Text'; -function ApprovalWorkflowSection({approvalWorkflow}: {approvalWorkflow: ApprovalWorkflow}) { +function ApprovalWorkflowSection({approvalWorkflow, policyId}: {approvalWorkflow: ApprovalWorkflow; policyId?: string}) { const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); + const openApprovalsEdit = useCallback( + () => Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EDIT.getRoute(policyId ?? '', approvalWorkflow.approvers[0].email)), + [approvalWorkflow.approvers, policyId], + ); return ( {}} + onPress={openApprovalsEdit} accessibilityLabel={translate('workflowsPage.addApprovalsTitle')} > diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx index 1a819b63623e..8bf949b57621 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx @@ -151,10 +151,13 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr subMenuItems: ( <> {/* TODO: Functionality for this button will be added in a future PR (https://github.com/Expensify/App/issues/45954) */} - {canUseAdvancedApproval && ( + {canUseAdvancedApproval ? ( <> {workflows.map((w) => ( - + ))} + ) : ( + Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_APPROVER.getRoute(route.params.policyID))} + shouldShowRightIcon + wrapperStyle={[styles.sectionMenuItemTopDescription, styles.mt3, styles.mbn3]} + brickRoadIndicator={hasApprovalError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} + /> )} ), From 822b5c55c88e0562d4455c33ac60508c7ca941b8 Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Wed, 31 Jul 2024 13:18:34 +0200 Subject: [PATCH 06/20] approverName and menuItem propagation problem --- src/components/ApprovalWorkflowSection.tsx | 2 ++ src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/ApprovalWorkflowSection.tsx b/src/components/ApprovalWorkflowSection.tsx index eb0d01754bcc..212c2da450c0 100644 --- a/src/components/ApprovalWorkflowSection.tsx +++ b/src/components/ApprovalWorkflowSection.tsx @@ -56,6 +56,7 @@ function ApprovalWorkflowSection({approvalWorkflow, policyId}: {approvalWorkflow iconWidth={20} iconFill={theme.icon} interactive={false} + onPress={openApprovalsEdit} /> {approvalWorkflow.approvers.map((approver) => ( @@ -72,6 +73,7 @@ function ApprovalWorkflowSection({approvalWorkflow, policyId}: {approvalWorkflow iconWidth={20} iconFill={theme.icon} interactive={false} + onPress={openApprovalsEdit} /> ))} diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx index 8bf949b57621..acb52a9b6715 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx @@ -61,11 +61,11 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr const [isCurrencyModalOpen, setIsCurrencyModalOpen] = useState(false); const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); + const policyApproverName = useMemo(() => PersonalDetailsUtils.getPersonalDetailByEmail(policyApproverEmail ?? '')?.displayName ?? policyApproverEmail, [policyApproverEmail]); const workflows = useMemo( () => convertPolicyEmployeesToApprovalWorkflows({employees: policy?.employeeList ?? {}, defaultApprover: policyApproverEmail ?? '', personalDetails: personalDetails ?? {}}), [personalDetails, policy?.employeeList, policyApproverEmail], ); - const displayNameForAuthorizedPayer = useMemo( () => PersonalDetailsUtils.getPersonalDetailByEmail(policy?.achAccount?.reimburser ?? '')?.displayName ?? policy?.achAccount?.reimburser, [policy?.achAccount?.reimburser], @@ -171,7 +171,7 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr ) : ( Date: Wed, 31 Jul 2024 13:37:57 +0200 Subject: [PATCH 07/20] toLocaleOrdinal fix, linking to approval section --- src/components/ApprovalWorkflowSection.tsx | 6 +++--- src/components/LocaleContextProvider.tsx | 9 +++++++-- src/libs/LocaleDigitUtils.ts | 4 ++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/components/ApprovalWorkflowSection.tsx b/src/components/ApprovalWorkflowSection.tsx index 212c2da450c0..19321047c696 100644 --- a/src/components/ApprovalWorkflowSection.tsx +++ b/src/components/ApprovalWorkflowSection.tsx @@ -15,7 +15,7 @@ import Text from './Text'; function ApprovalWorkflowSection({approvalWorkflow, policyId}: {approvalWorkflow: ApprovalWorkflow; policyId?: string}) { const styles = useThemeStyles(); const theme = useTheme(); - const {translate} = useLocalize(); + const {translate, toLocaleOrdinal} = useLocalize(); const openApprovalsEdit = useCallback( () => Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EDIT.getRoute(policyId ?? '', approvalWorkflow.approvers[0].email)), [approvalWorkflow.approvers, policyId], @@ -59,11 +59,11 @@ function ApprovalWorkflowSection({approvalWorkflow, policyId}: {approvalWorkflow onPress={openApprovalsEdit} /> - {approvalWorkflow.approvers.map((approver) => ( + {approvalWorkflow.approvers.map((approver, index) => ( string; /** Formats a number into its localized ordinal representation */ - toLocaleOrdinal: (number: number) => string; + toLocaleOrdinal: (number: number, returnWords: boolean) => string; /** Gets the standard digit corresponding to a locale digit */ fromLocaleDigit: (digit: string) => string; @@ -101,7 +101,12 @@ function LocaleContextProvider({preferredLocale, currentUserPersonalDetails, chi const toLocaleDigit = useMemo(() => (digit) => LocaleDigitUtils.toLocaleDigit(locale, digit), [locale]); - const toLocaleOrdinal = useMemo(() => (number) => LocaleDigitUtils.toLocaleOrdinal(locale, number), [locale]); + const toLocaleOrdinal = useMemo( + () => + (number, returnWords = false) => + LocaleDigitUtils.toLocaleOrdinal(locale, number, returnWords), + [locale], + ); const fromLocaleDigit = useMemo(() => (localeDigit) => LocaleDigitUtils.fromLocaleDigit(locale, localeDigit), [locale]); diff --git a/src/libs/LocaleDigitUtils.ts b/src/libs/LocaleDigitUtils.ts index 2bb57922a6c9..91dc80676c76 100644 --- a/src/libs/LocaleDigitUtils.ts +++ b/src/libs/LocaleDigitUtils.ts @@ -1,4 +1,4 @@ -import type {Integer, ValueOf} from 'type-fest'; +import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import * as Localize from './Localize'; @@ -98,7 +98,7 @@ function toLocaleOrdinal(locale: Locale, number: number, returnWords = false): s const suffix = Localize.translate(locale, suffixKey); - return `${number}${suffix}`; + return returnWords ? suffix : `${number}${suffix}`; } export {toLocaleDigit, toLocaleOrdinal, fromLocaleDigit}; From 7d6b856d65033b905f00ebcd588f2b8ee59d1e26 Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Wed, 31 Jul 2024 13:45:02 +0200 Subject: [PATCH 08/20] Temp deleted, sections' props added --- src/components/ApprovalWorkflowSection.tsx | 9 +++++++-- src/components/Temp.tsx | 8 -------- 2 files changed, 7 insertions(+), 10 deletions(-) delete mode 100644 src/components/Temp.tsx diff --git a/src/components/ApprovalWorkflowSection.tsx b/src/components/ApprovalWorkflowSection.tsx index 19321047c696..e66f4a394b94 100644 --- a/src/components/ApprovalWorkflowSection.tsx +++ b/src/components/ApprovalWorkflowSection.tsx @@ -12,7 +12,12 @@ import MenuItem from './MenuItem'; import PressableWithFeedback from './Pressable/PressableWithFeedback'; import Text from './Text'; -function ApprovalWorkflowSection({approvalWorkflow, policyId}: {approvalWorkflow: ApprovalWorkflow; policyId?: string}) { +type ApprovalWorkflowSectionProps = { + approvalWorkflow: ApprovalWorkflow; + policyId?: string; +}; + +function ApprovalWorkflowSection({approvalWorkflow, policyId}: ApprovalWorkflowSectionProps) { const styles = useThemeStyles(); const theme = useTheme(); const {translate, toLocaleOrdinal} = useLocalize(); @@ -63,7 +68,7 @@ function ApprovalWorkflowSection({approvalWorkflow, policyId}: {approvalWorkflow TEMP; -} - -export default Temp; From 42c370faa174d9da1df4aa74dd2faffcb78d9a28 Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Wed, 31 Jul 2024 14:27:24 +0200 Subject: [PATCH 09/20] undo react-native.d.ts change --- src/types/modules/react-native.d.ts | 359 +++++++++++++++++++++++++++- 1 file changed, 357 insertions(+), 2 deletions(-) diff --git a/src/types/modules/react-native.d.ts b/src/types/modules/react-native.d.ts index 20f6e8ed425a..87628fded0cc 100644 --- a/src/types/modules/react-native.d.ts +++ b/src/types/modules/react-native.d.ts @@ -4,8 +4,9 @@ /* eslint-disable @typescript-eslint/consistent-type-definitions */ // eslint-disable-next-line no-restricted-imports -import '@types/react-native-web'; -import type {TargetedEvent} from 'react-native'; +import type {CSSProperties, FocusEventHandler, KeyboardEventHandler, MouseEventHandler, PointerEventHandler, TouchEventHandler, UIEventHandler, WheelEventHandler} from 'react'; +import 'react-native'; +import type {GestureResponderEvent, LayoutRectangle, NativeSyntheticEvent, TargetedEvent} from 'react-native'; import type {BootSplashModule} from '@libs/BootSplash/types'; import type {EnvironmentCheckerModule} from '@libs/Environment/betaChecker/types'; import type StartupTimer from '@libs/StartupTimer/types'; @@ -21,6 +22,360 @@ type RNTextInputResetModule = { }; declare module 'react-native' { + // <------ REACT NATIVE WEB (0.19.12) ------> + // Extracted from react-native-web, packages/react-native-web/src/exports/View/types.js + type idRef = string; + type idRefList = idRef | idRef[]; + + // https://necolas.github.io/react-native-web/docs/accessibility/#accessibility-props-api + // Extracted from react-native-web, packages/react-native-web/src/exports/View/types.js + interface AccessibilityProps { + 'aria-activedescendant'?: idRef; + 'aria-atomic'?: boolean; + 'aria-autocomplete'?: 'none' | 'list' | 'inline' | 'both'; + 'aria-busy'?: boolean; + 'aria-checked'?: boolean | 'mixed'; + 'aria-colcount'?: number; + 'aria-colindex'?: number; + 'aria-colspan'?: number; + 'aria-controls'?: idRef; + 'aria-current'?: boolean | 'page' | 'step' | 'location' | 'date' | 'time'; + 'aria-describedby'?: idRef; + 'aria-details'?: idRef; + 'aria-disabled'?: boolean; + 'aria-errormessage'?: idRef; + 'aria-expanded'?: boolean; + 'aria-flowto'?: idRef; + 'aria-haspopup'?: 'dialog' | 'grid' | 'listbox' | 'menu' | 'tree' | false; + 'aria-hidden'?: boolean; + 'aria-invalid'?: boolean; + 'aria-keyshortcuts'?: string; + 'aria-label'?: string; + 'aria-labelledby'?: idRef; + 'aria-level'?: number; + 'aria-live'?: 'assertive' | 'off' | 'polite'; + 'aria-modal'?: boolean; + 'aria-multiline'?: boolean; + 'aria-multiselectable'?: boolean; + 'aria-orientation'?: 'horizontal' | 'vertical'; + 'aria-owns'?: idRef; + 'aria-placeholder'?: string; + 'aria-posinset'?: number; + 'aria-pressed'?: boolean | 'mixed'; + 'aria-readonly'?: boolean; + 'aria-required'?: boolean; + 'aria-roledescription'?: string; + 'aria-rowcount'?: number; + 'aria-rowindex'?: number; + 'aria-rowspan'?: number; + 'aria-selected'?: boolean; + 'aria-setsize'?: number; + 'aria-sort'?: 'ascending' | 'descending' | 'none' | 'other'; + 'aria-valuemax'?: number; + 'aria-valuemin'?: number; + 'aria-valuenow'?: number; + 'aria-valuetext'?: string; + + // @deprecated + accessibilityActiveDescendant?: idRef; + accessibilityAtomic?: boolean; + accessibilityAutoComplete?: 'none' | 'list' | 'inline' | 'both'; + accessibilityBusy?: boolean; + accessibilityChecked?: boolean | 'mixed'; + accessibilityColumnCount?: number; + accessibilityColumnIndex?: number; + accessibilityColumnSpan?: number; + accessibilityControls?: idRefList; + accessibilityCurrent?: boolean | 'page' | 'step' | 'location' | 'date' | 'time'; + accessibilityDescribedBy?: idRefList; + accessibilityDetails?: idRef; + accessibilityDisabled?: boolean; + accessibilityErrorMessage?: idRef; + accessibilityExpanded?: boolean; + accessibilityFlowTo?: idRefList; + accessibilityHasPopup?: 'dialog' | 'grid' | 'listbox' | 'menu' | 'tree' | false; + accessibilityHidden?: boolean; + accessibilityInvalid?: boolean; + accessibilityKeyShortcuts?: string[]; + accessibilityLabel?: string; + accessibilityLabelledBy?: idRef; + accessibilityLevel?: number; + accessibilityLiveRegion?: 'assertive' | 'none' | 'polite'; + accessibilityModal?: boolean; + accessibilityMultiline?: boolean; + accessibilityMultiSelectable?: boolean; + accessibilityOrientation?: 'horizontal' | 'vertical'; + accessibilityOwns?: idRefList; + accessibilityPlaceholder?: string; + accessibilityPosInSet?: number; + accessibilityPressed?: boolean | 'mixed'; + accessibilityReadOnly?: boolean; + accessibilityRequired?: boolean; + accessibilityRoleDescription?: string; + accessibilityRowCount?: number; + accessibilityRowIndex?: number; + accessibilityRowSpan?: number; + accessibilitySelected?: boolean; + accessibilitySetSize?: number; + accessibilitySort?: 'ascending' | 'descending' | 'none' | 'other'; + accessibilityValueMax?: number; + accessibilityValueMin?: number; + accessibilityValueNow?: number; + accessibilityValueText?: string; + } + + // https://necolas.github.io/react-native-web/docs/interactions/#pointerevent-props-api + // Extracted properties from react-native-web, packages/react-native-web/src/exports/View/types.js and packages/react-native-web/src/modules/forwardedProps/index.js + // Extracted types from @types/react, index.d.ts + interface ClickProps { + onClick?: MouseEventHandler; + onAuxClick?: MouseEventHandler; + onContextMenu?: MouseEventHandler; + onGotPointerCapture?: PointerEventHandler; + onLostPointerCapture?: PointerEventHandler; + onPointerCancel?: PointerEventHandler; + onPointerDown?: PointerEventHandler; + onPointerEnter?: PointerEventHandler; + onPointerMove?: PointerEventHandler; + onPointerLeave?: PointerEventHandler; + onPointerOut?: PointerEventHandler; + onPointerOver?: PointerEventHandler; + onPointerUp?: PointerEventHandler; + onScroll?: UIEventHandler; + onWheel?: WheelEventHandler; + } + + // https://necolas.github.io/react-native-web/docs/interactions/#focusevent-props-api + // Extracted properties from react-native-web, packages/react-native-web/src/exports/View/types.js and packages/react-native-web/src/modules/forwardedProps/index.js + // Extracted types from @types/react, index.d.ts + interface FocusProps { + onBlur?: FocusEventHandler; + onFocus?: FocusEventHandler; + } + + // https://necolas.github.io/react-native-web/docs/interactions/#keyboardevent-props-api + // Extracted properties from react-native-web, packages/react-native-web/src/exports/View/types.js and packages/react-native-web/src/modules/forwardedProps/index.js + // Extracted types from @types/react, index.d.ts + interface KeyboardProps { + onKeyDown?: KeyboardEventHandler; + onKeyDownCapture?: KeyboardEventHandler; + onKeyUp?: KeyboardEventHandler; + onKeyUpCapture?: KeyboardEventHandler; + } + + // Extracted properties from react-native-web, packages/react-native-web/src/exports/View/types.js and packages/react-native-web/src/modules/forwardedProps/index.js + // Extracted types from @types/react, index.d.ts + interface MouseProps { + onMouseDown?: MouseEventHandler; + onMouseEnter?: MouseEventHandler; + onMouseLeave?: MouseEventHandler; + onMouseMove?: MouseEventHandler; + onMouseOver?: MouseEventHandler; + onMouseOut?: MouseEventHandler; + onMouseUp?: MouseEventHandler; + } + + // Extracted properties from react-native-web, packages/react-native-web/src/exports/View/types.js and packages/react-native-web/src/modules/forwardedProps/index.js + // Extracted types from @types/react, index.d.ts + interface TouchProps { + onTouchCancel?: TouchEventHandler; + onTouchCancelCapture?: TouchEventHandler; + onTouchEnd?: TouchEventHandler; + onTouchEndCapture?: TouchEventHandler; + onTouchMove?: TouchEventHandler; + onTouchMoveCapture?: TouchEventHandler; + onTouchStart?: TouchEventHandler; + onTouchStartCapture?: TouchEventHandler; + } + + // https://necolas.github.io/react-native-web/docs/interactions/#responderevent-props-api + // Extracted from react-native-web, packages/react-native-web/src/modules/useResponderEvents/ResponderTouchHistoryStore.js + type TouchRecord = { + currentPageX: number; + currentPageY: number; + currentTimeStamp: number; + previousPageX: number; + previousPageY: number; + previousTimeStamp: number; + startPageX: number; + startPageY: number; + startTimeStamp: number; + touchActive: boolean; + }; + + // https://necolas.github.io/react-native-web/docs/interactions/#responderevent-props-api + // Extracted from react-native-web, packages/react-native-web/src/modules/useResponderEvents/ResponderTouchHistoryStore.js + type TouchHistory = Readonly<{ + indexOfSingleActiveTouch: number; + mostRecentTimeStamp: number; + numberActiveTouches: number; + touchBank: TouchRecord[]; + }>; + + // https://necolas.github.io/react-native-web/docs/interactions/#responderevent-props-api + // Extracted from react-native-web, packages/react-native-web/src/modules/useResponderEvents/createResponderEvent.js + type ResponderEvent = { + bubbles: boolean; + cancelable: boolean; + currentTarget?: unknown; // changed from "any" to "unknown" + defaultPrevented?: boolean; + dispatchConfig: { + registrationName?: string; + phasedRegistrationNames?: { + bubbled: string; + captured: string; + }; + }; + eventPhase?: number; + isDefaultPrevented: () => boolean; + isPropagationStopped: () => boolean; + isTrusted?: boolean; + preventDefault: () => void; + stopPropagation: () => void; + nativeEvent: TouchEvent; + persist: () => void; + target?: unknown; // changed from "any" to "unknown" + timeStamp: number; + touchHistory: TouchHistory; + }; + + // https://necolas.github.io/react-native-web/docs/interactions/#responderevent-props-api + // Extracted from react-native-web, packages/react-native-web/src/modules/useResponderEvents/ResponderSystem.js + interface ResponderProps { + // Direct responder events dispatched directly to responder. Do not bubble. + onResponderEnd?: (e: ResponderEvent) => void; + onResponderGrant?: (e: ResponderEvent) => void | boolean; + onResponderMove?: (e: ResponderEvent) => void; + onResponderRelease?: (e: ResponderEvent) => void; + onResponderReject?: (e: ResponderEvent) => void; + onResponderStart?: (e: ResponderEvent) => void; + onResponderTerminate?: (e: ResponderEvent) => void; + onResponderTerminationRequest?: (e: ResponderEvent) => boolean; + + // On pointer down, should this element become the responder? + onStartShouldSetResponder?: (e: ResponderEvent) => boolean; + onStartShouldSetResponderCapture?: (e: ResponderEvent) => boolean; + + // On pointer move, should this element become the responder? + onMoveShouldSetResponder?: (e: ResponderEvent) => boolean; + onMoveShouldSetResponderCapture?: (e: ResponderEvent) => boolean; + + // On scroll, should this element become the responder? Do no bubble + onScrollShouldSetResponder?: (e: ResponderEvent) => boolean; + onScrollShouldSetResponderCapture?: (e: ResponderEvent) => boolean; + + // On text selection change, should this element become the responder? + onSelectionChangeShouldSetResponder?: (e: ResponderEvent) => boolean; + onSelectionChangeShouldSetResponderCapture?: (e: ResponderEvent) => boolean; + } + + // @ts-expect-error We override this type in order to provide "target" to onLayout event. + type LayoutChangeEvent = NativeSyntheticEvent<{layout: LayoutRectangle; target?: unknown}>; + + interface EventProps extends ClickProps, FocusProps, KeyboardProps, MouseProps, TouchProps, ResponderProps {} + + /** + * Shared props + * Extracted from react-native-web, packages/react-native-web/src/exports/View/types.js + */ + interface WebSharedProps extends AccessibilityProps, EventProps { + dataSet?: Record; + href?: string; + hrefAttrs?: { + download?: boolean; + rel?: string; + target?: string; + }; + tabIndex?: 0 | -1; + lang?: string; + } + + /** + * View + * Extracted from react-native-web, packages/react-native-web/src/exports/View/types.js + */ + interface WebViewProps extends WebSharedProps { + dir?: 'ltr' | 'rtl'; + } + interface ViewProps extends WebViewProps {} + + /** + * Text + * Extracted from react-native-web, packages/react-native-web/src/exports/Text/types.js + */ + interface WebTextProps extends WebSharedProps { + dir?: 'auto' | 'ltr' | 'rtl'; + } + interface TextProps extends WebTextProps {} + + /** + * TextInput + * Extracted from react-native-web, packages/react-native-web/src/exports/TextInput/types.js + */ + interface WebTextInputProps extends WebSharedProps { + dir?: 'auto' | 'ltr' | 'rtl'; + disabled?: boolean; + } + interface TextInputProps extends WebTextInputProps {} + + /** + * Image + * Extracted from react-native-web, packages/react-native-web/src/exports/Image/types.js + */ + interface WebImageProps extends WebSharedProps { + dir?: 'ltr' | 'rtl'; + draggable?: boolean; + } + interface ImageProps extends WebImageProps {} + + /** + * ScrollView + * Extracted from react-native-web, packages/react-native-web/src/exports/ScrollView/ScrollViewBase.js + */ + interface WebScrollViewProps extends WebSharedProps {} + interface ScrollViewProps extends WebScrollViewProps {} + + /** + * Pressable + */ + // https://necolas.github.io/react-native-web/docs/pressable/#interactionstate + // Extracted from react-native-web, packages/react-native-web/src/exports/Pressable/index.js + interface WebPressableStateCallbackType { + readonly focused: boolean; + readonly hovered: boolean; + readonly pressed: boolean; + } + interface PressableStateCallbackType extends WebPressableStateCallbackType {} + + // Extracted from react-native-web, packages/react-native-web/src/exports/Pressable/index.js + interface WebPressableProps extends WebSharedProps { + /** Duration (in milliseconds) from `onPressStart` is called after pointerdown. */ + delayPressIn?: number; + /** Duration (in milliseconds) from `onPressEnd` is called after pointerup. */ + delayPressOut?: number; + /** Called when a touch is moving, after `onPressIn`. */ + onPressMove?: (event: GestureResponderEvent) => void; + } + interface PressableProps extends WebPressableProps {} + + /** + * Styles + */ + // We extend CSSProperties (alias to "csstype" library) which provides all CSS style properties for Web, + // but properties that are already defined on RN won't be overrided / augmented. + interface WebStyle extends CSSProperties { + // https://necolas.github.io/react-native-web/docs/styling/#non-standard-properties + // Exclusive to react-native-web, "pointerEvents" already included on RN + animationKeyframes?: string | Record; + writingDirection?: 'auto' | 'ltr' | 'rtl'; + enableBackground?: string; + } + + interface ViewStyle extends WebStyle {} + interface TextStyle extends WebStyle {} + interface ImageStyle extends WebStyle {} + // <------ REACT NATIVE WEB (0.19.12) ------> + interface TextInputFocusEventData extends TargetedEvent { text: string; eventCount: number; From d8291fd5d4ab9aec146c25dc7212456d9eda6a5d Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Wed, 31 Jul 2024 14:31:55 +0200 Subject: [PATCH 10/20] undo package.json --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index d2166e38e28a..ca88c73a4328 100644 --- a/package.json +++ b/package.json @@ -239,7 +239,6 @@ "@types/node": "^20.11.5", "@types/pusher-js": "^5.1.0", "@types/react": "18.2.45", - "@types/react-native-web": "0.73.0", "@types/react-beautiful-dnd": "^13.1.4", "@types/react-collapse": "^5.0.1", "@types/react-dom": "^18.2.4", From 9659c6198986b2c422d7b4347cfc222cf2839e77 Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Wed, 31 Jul 2024 14:42:45 +0200 Subject: [PATCH 11/20] toLocale fix2, updates --- src/components/LocaleContextProvider.tsx | 2 +- .../workflows/WorkspaceWorkflowsPage.tsx | 58 +++++++++---------- 2 files changed, 28 insertions(+), 32 deletions(-) diff --git a/src/components/LocaleContextProvider.tsx b/src/components/LocaleContextProvider.tsx index 0d7cc760f672..f4a1ee7b25af 100644 --- a/src/components/LocaleContextProvider.tsx +++ b/src/components/LocaleContextProvider.tsx @@ -50,7 +50,7 @@ type LocaleContextProps = { toLocaleDigit: (digit: string) => string; /** Formats a number into its localized ordinal representation */ - toLocaleOrdinal: (number: number, returnWords: boolean) => string; + toLocaleOrdinal: (number: number, returnWords?: boolean) => string; /** Gets the standard digit corresponding to a locale digit */ fromLocaleDigit: (digit: string) => string; diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx index acb52a9b6715..919e99ecf679 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx @@ -148,40 +148,36 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr onToggle: (isEnabled: boolean) => { Policy.setWorkspaceApprovalMode(route.params.policyID, policy?.owner ?? '', isEnabled ? CONST.POLICY.APPROVAL_MODE.BASIC : CONST.POLICY.APPROVAL_MODE.OPTIONAL); }, - subMenuItems: ( + subMenuItems: canUseAdvancedApproval ? ( <> - {/* TODO: Functionality for this button will be added in a future PR (https://github.com/Expensify/App/issues/45954) */} - {canUseAdvancedApproval ? ( - <> - {workflows.map((w) => ( - - ))} - - - ) : ( - Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_APPROVER.getRoute(route.params.policyID))} - shouldShowRightIcon - wrapperStyle={[styles.sectionMenuItemTopDescription, styles.mt3, styles.mbn3]} - brickRoadIndicator={hasApprovalError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} + {workflows.map((w) => ( + - )} + ))} + {/* TODO: Functionality for this button will be added in a future PR (https://github.com/Expensify/App/issues/45954) */} + + ) : ( + Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_APPROVER.getRoute(route.params.policyID))} + shouldShowRightIcon + wrapperStyle={[styles.sectionMenuItemTopDescription, styles.mt3, styles.mbn3]} + brickRoadIndicator={hasApprovalError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} + /> ), isActive: (policy?.approvalMode === CONST.POLICY.APPROVAL_MODE.BASIC && !hasApprovalError) ?? false, pendingAction: policy?.pendingFields?.approvalMode, From 741cd37f9ad1f88c94594117c1b5b100d7a91fe5 Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Wed, 31 Jul 2024 15:42:31 +0200 Subject: [PATCH 12/20] unclickable MenuItem fixed --- src/components/ApprovalWorkflowSection.tsx | 4 ++-- src/components/MenuItem.tsx | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/ApprovalWorkflowSection.tsx b/src/components/ApprovalWorkflowSection.tsx index e66f4a394b94..5e4865e2601e 100644 --- a/src/components/ApprovalWorkflowSection.tsx +++ b/src/components/ApprovalWorkflowSection.tsx @@ -60,8 +60,8 @@ function ApprovalWorkflowSection({approvalWorkflow, policyId}: ApprovalWorkflowS iconHeight={20} iconWidth={20} iconFill={theme.icon} - interactive={false} onPress={openApprovalsEdit} + shouldRemoveBackground /> {approvalWorkflow.approvers.map((approver, index) => ( @@ -77,8 +77,8 @@ function ApprovalWorkflowSection({approvalWorkflow, policyId}: ApprovalWorkflowS iconHeight={20} iconWidth={20} iconFill={theme.icon} - interactive={false} onPress={openApprovalsEdit} + shouldRemoveBackground /> ))} diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index 4865617ddb2e..8af6cd492c03 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -242,6 +242,9 @@ type MenuItemBaseProps = { /** Should we grey out the menu item when it is disabled? */ shouldGreyOutWhenDisabled?: boolean; + /** Should we remove the background color of the menu item */ + shouldRemoveBackground?: boolean; + /** Should we use default cursor for disabled content */ shouldUseDefaultCursorWhenDisabled?: boolean; @@ -377,6 +380,7 @@ function MenuItem( shouldRenderAsHTML = false, shouldEscapeText = undefined, shouldGreyOutWhenDisabled = true, + shouldRemoveBackground = false, shouldUseDefaultCursorWhenDisabled = false, shouldShowLoadingSpinnerIcon = false, isAnonymousAction = false, @@ -537,11 +541,12 @@ function MenuItem( containerStyle, combinedStyle, !interactive && styles.cursorDefault, - StyleUtils.getButtonBackgroundColorStyle(getButtonState(focused || isHovered, pressed, success, disabled, interactive), true), + !shouldRemoveBackground && + StyleUtils.getButtonBackgroundColorStyle(getButtonState(focused || isHovered, pressed, success, disabled, interactive), true), ...(Array.isArray(wrapperStyle) ? wrapperStyle : [wrapperStyle]), !focused && (isHovered || pressed) && hoverAndPressStyle, shouldGreyOutWhenDisabled && disabled && styles.buttonOpacityDisabled, - isHovered && interactive && !focused && !pressed && styles.hoveredComponentBG, + isHovered && interactive && !focused && !pressed && !shouldRemoveBackground && styles.hoveredComponentBG, ] as StyleProp } disabledStyle={shouldUseDefaultCursorWhenDisabled && [styles.cursorDefault]} From ca527d1aea58a5d75afbb32639c16f07556387c1 Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Wed, 31 Jul 2024 15:59:45 +0200 Subject: [PATCH 13/20] approver naming fixed --- src/components/ApprovalWorkflowSection.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/ApprovalWorkflowSection.tsx b/src/components/ApprovalWorkflowSection.tsx index 5e4865e2601e..605d67dd7462 100644 --- a/src/components/ApprovalWorkflowSection.tsx +++ b/src/components/ApprovalWorkflowSection.tsx @@ -9,7 +9,7 @@ import type ApprovalWorkflow from '@src/types/onyx/ApprovalWorkflow'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import MenuItem from './MenuItem'; -import PressableWithFeedback from './Pressable/PressableWithFeedback'; +import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; import Text from './Text'; type ApprovalWorkflowSectionProps = { @@ -25,9 +25,14 @@ function ApprovalWorkflowSection({approvalWorkflow, policyId}: ApprovalWorkflowS () => Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EDIT.getRoute(policyId ?? '', approvalWorkflow.approvers[0].email)), [approvalWorkflow.approvers, policyId], ); + const approverTitle = useCallback( + (index: number) => + approvalWorkflow.approvers.length > 1 ? `${toLocaleOrdinal(index + 1, true)} ${translate('workflowsPage.approver').toLowerCase()}` : `${translate('workflowsPage.approver')}`, + [approvalWorkflow.approvers.length, toLocaleOrdinal, translate], + ); return ( - - + ); } From aa3d60c7240e0f863715cc18164a8c606c41e5fd Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 1 Aug 2024 15:34:04 +0200 Subject: [PATCH 14/20] Fix design issues --- src/components/ApprovalWorkflowSection.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/ApprovalWorkflowSection.tsx b/src/components/ApprovalWorkflowSection.tsx index 605d67dd7462..bb3d99de99a1 100644 --- a/src/components/ApprovalWorkflowSection.tsx +++ b/src/components/ApprovalWorkflowSection.tsx @@ -3,6 +3,7 @@ import {View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; import Navigation from '@libs/Navigation/Navigation'; import ROUTES from '@src/ROUTES'; import type ApprovalWorkflow from '@src/types/onyx/ApprovalWorkflow'; @@ -21,6 +22,7 @@ function ApprovalWorkflowSection({approvalWorkflow, policyId}: ApprovalWorkflowS const styles = useThemeStyles(); const theme = useTheme(); const {translate, toLocaleOrdinal} = useLocalize(); + const {isSmallScreenWidth} = useWindowDimensions(); const openApprovalsEdit = useCallback( () => Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EDIT.getRoute(policyId ?? '', approvalWorkflow.approvers[0].email)), [approvalWorkflow.approvers, policyId], @@ -34,7 +36,7 @@ function ApprovalWorkflowSection({approvalWorkflow, policyId}: ApprovalWorkflowS return ( @@ -80,7 +82,7 @@ function ApprovalWorkflowSection({approvalWorkflow, policyId}: ApprovalWorkflowS description={approver.displayName} icon={Expensicons.UserCheck} iconHeight={20} - iconWidth={20} + iconWidth={21} iconFill={theme.icon} onPress={openApprovalsEdit} shouldRemoveBackground From 6564afe6b7019970e05f34e29dca645c3ee8115c Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 1 Aug 2024 18:41:14 +0200 Subject: [PATCH 15/20] Update user-check icon --- assets/images/user-check.svg | 17 +++++++++-------- src/components/ApprovalWorkflowSection.tsx | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/assets/images/user-check.svg b/assets/images/user-check.svg index e8dd4a3ef09c..4c4a18be4096 100644 --- a/assets/images/user-check.svg +++ b/assets/images/user-check.svg @@ -1,9 +1,10 @@ - - - - - - - - + + + + + diff --git a/src/components/ApprovalWorkflowSection.tsx b/src/components/ApprovalWorkflowSection.tsx index bb3d99de99a1..fffb6b5156a4 100644 --- a/src/components/ApprovalWorkflowSection.tsx +++ b/src/components/ApprovalWorkflowSection.tsx @@ -82,7 +82,7 @@ function ApprovalWorkflowSection({approvalWorkflow, policyId}: ApprovalWorkflowS description={approver.displayName} icon={Expensicons.UserCheck} iconHeight={20} - iconWidth={21} + iconWidth={20} iconFill={theme.icon} onPress={openApprovalsEdit} shouldRemoveBackground From 22df581cc2ab37e9f2ceaf0f8187a17a2f49a8cf Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 1 Aug 2024 20:19:15 +0200 Subject: [PATCH 16/20] Change the icon to the correct one --- assets/images/user-check.svg | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/assets/images/user-check.svg b/assets/images/user-check.svg index 4c4a18be4096..2da67de751f4 100644 --- a/assets/images/user-check.svg +++ b/assets/images/user-check.svg @@ -1,10 +1,9 @@ - - - + viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve"> + From fc835a13f655acd507c4625a538197c0cfa4b6c6 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 2 Aug 2024 18:21:51 +0200 Subject: [PATCH 17/20] Move vertical line styles --- src/components/ApprovalWorkflowSection.tsx | 2 +- src/styles/index.ts | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/ApprovalWorkflowSection.tsx b/src/components/ApprovalWorkflowSection.tsx index fffb6b5156a4..4c41328ebf9c 100644 --- a/src/components/ApprovalWorkflowSection.tsx +++ b/src/components/ApprovalWorkflowSection.tsx @@ -73,7 +73,7 @@ function ApprovalWorkflowSection({approvalWorkflow, policyId}: ApprovalWorkflowS {approvalWorkflow.approvers.map((approver, index) => ( - + width: 184, height: 112, }, + + workflowApprovalVerticalLine: { + height: 16, + width: 1, + marginLeft: 19, + backgroundColor: theme.border, + }, } satisfies Styles); type ThemeStyles = ReturnType; From 04f7776d59e1f52ec386ab4c3687253411c52f61 Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Mon, 5 Aug 2024 10:35:11 +0200 Subject: [PATCH 18/20] toLocaleString refactor --- src/components/LocaleContextProvider.tsx | 4 ++-- src/libs/LocaleDigitUtils.ts | 15 +++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/components/LocaleContextProvider.tsx b/src/components/LocaleContextProvider.tsx index f4a1ee7b25af..383784a468d7 100644 --- a/src/components/LocaleContextProvider.tsx +++ b/src/components/LocaleContextProvider.tsx @@ -103,8 +103,8 @@ function LocaleContextProvider({preferredLocale, currentUserPersonalDetails, chi const toLocaleOrdinal = useMemo( () => - (number, returnWords = false) => - LocaleDigitUtils.toLocaleOrdinal(locale, number, returnWords), + (number, writtenOrdinals = false) => + LocaleDigitUtils.toLocaleOrdinal(locale, number, writtenOrdinals), [locale], ); diff --git a/src/libs/LocaleDigitUtils.ts b/src/libs/LocaleDigitUtils.ts index 91dc80676c76..0362f4aa963f 100644 --- a/src/libs/LocaleDigitUtils.ts +++ b/src/libs/LocaleDigitUtils.ts @@ -73,8 +73,11 @@ function fromLocaleDigit(locale: Locale, localeDigit: string): string { /** * Formats a number into its localized ordinal representation i.e 1st, 2nd etc + * @param locale - The locale to use for formatting + * @param number - The number to format + * @param writtenOrdinals - If true, returns the written ordinal (e.g. "first", "second") for numbers 1-10 */ -function toLocaleOrdinal(locale: Locale, number: number, returnWords = false): string { +function toLocaleOrdinal(locale: Locale, number: number, writtenOrdinals = false): string { // Defaults to "other" suffix or "th" in English let suffixKey: TranslationPaths = 'workflowsPage.frequencies.ordinals.other'; @@ -84,6 +87,10 @@ function toLocaleOrdinal(locale: Locale, number: number, returnWords = false): s // Calculate last two digits to handle exceptions in the 11-13 range const lastTwoDigits = number % 100; + if (writtenOrdinals && number >= 1 && number <= 10) { + return Localize.translate(locale, `workflowsPage.frequencies.ordinals.${number}` as TranslationPaths); + } + if (lastDigit === 1 && lastTwoDigits !== 11) { suffixKey = 'workflowsPage.frequencies.ordinals.one'; } else if (lastDigit === 2 && lastTwoDigits !== 12) { @@ -92,13 +99,9 @@ function toLocaleOrdinal(locale: Locale, number: number, returnWords = false): s suffixKey = 'workflowsPage.frequencies.ordinals.few'; } - if (returnWords && number >= 1 && number <= 10) { - suffixKey = `workflowsPage.frequencies.ordinals.${number}` as TranslationPaths; - } - const suffix = Localize.translate(locale, suffixKey); - return returnWords ? suffix : `${number}${suffix}`; + return `${number}${suffix}`; } export {toLocaleDigit, toLocaleOrdinal, fromLocaleDigit}; From 5478c6eb75f8ad65e33387349d980a53453800f9 Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Mon, 5 Aug 2024 15:05:24 +0200 Subject: [PATCH 19/20] safeguard approvers' keys --- src/components/ApprovalWorkflowSection.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ApprovalWorkflowSection.tsx b/src/components/ApprovalWorkflowSection.tsx index 4c41328ebf9c..369b78f8117f 100644 --- a/src/components/ApprovalWorkflowSection.tsx +++ b/src/components/ApprovalWorkflowSection.tsx @@ -72,7 +72,8 @@ function ApprovalWorkflowSection({approvalWorkflow, policyId}: ApprovalWorkflowS /> {approvalWorkflow.approvers.map((approver, index) => ( - + // eslint-disable-next-line react/no-array-index-key + Date: Mon, 5 Aug 2024 16:40:07 +0200 Subject: [PATCH 20/20] props annotations --- src/components/ApprovalWorkflowSection.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/ApprovalWorkflowSection.tsx b/src/components/ApprovalWorkflowSection.tsx index 369b78f8117f..899e83c9440b 100644 --- a/src/components/ApprovalWorkflowSection.tsx +++ b/src/components/ApprovalWorkflowSection.tsx @@ -14,7 +14,10 @@ import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; import Text from './Text'; type ApprovalWorkflowSectionProps = { + /** Single workflow displayed in this component */ approvalWorkflow: ApprovalWorkflow; + + /** ID of the policy */ policyId?: string; };