diff --git a/apps/mobile/app/(app)/_layout.tsx b/apps/mobile/app/(app)/_layout.tsx index f4c8b26b..4367bbc2 100644 --- a/apps/mobile/app/(app)/_layout.tsx +++ b/apps/mobile/app/(app)/_layout.tsx @@ -116,7 +116,7 @@ export default function AuthenticatedLayout() { diff --git a/apps/mobile/app/(app)/new-record.tsx b/apps/mobile/app/(app)/new-record.tsx index af7b647e..61911b41 100644 --- a/apps/mobile/app/(app)/new-record.tsx +++ b/apps/mobile/app/(app)/new-record.tsx @@ -1,5 +1,21 @@ -import { Text } from 'react-native' +import { NumericPad } from '@/components/numeric-pad' +import { TextTicker } from '@/components/text-ticker' +import { useState } from 'react' +import { View } from 'react-native' export default function NewRecordScreen() { - return New Record + const [value, setValue] = useState(0) + return ( + + + + + + + ) } diff --git a/apps/mobile/components/numeric-pad/index.ts b/apps/mobile/components/numeric-pad/index.ts new file mode 100644 index 00000000..a1c056e7 --- /dev/null +++ b/apps/mobile/components/numeric-pad/index.ts @@ -0,0 +1 @@ +export * from './numeric-pad' diff --git a/apps/mobile/components/numeric-pad/numeric-pad.tsx b/apps/mobile/components/numeric-pad/numeric-pad.tsx new file mode 100644 index 00000000..46a31ed5 --- /dev/null +++ b/apps/mobile/components/numeric-pad/numeric-pad.tsx @@ -0,0 +1,73 @@ +import { DeleteIcon } from 'lucide-react-native' +import { View } from 'react-native' +import { Button } from '../ui/button' +import { Text } from '../ui/text' + +const buttonKeys = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '000', '0'] + +type NumericPadProps = { + disabled?: boolean + value: number + onValueChange?: (value: number) => void + maxValue?: number +} + +export function NumericPad({ + disabled, + value = 0, + onValueChange, + maxValue = 9999999999, +}: NumericPadProps) { + function handleKeyPress(key: string) { + let newValue: number + + if (key === '000') { + newValue = value * 1000 + } else { + newValue = value * 10 + Number(key) + } + + if (newValue > maxValue) { + return + } + + onValueChange?.(newValue) + } + + function handleDelete() { + const newValue = Math.floor(value / 10) + onValueChange?.(newValue) + } + + function handleClear() { + onValueChange?.(0) + } + + return ( + + {buttonKeys.map((buttonKey) => ( + + + + ))} + + + + + ) +} diff --git a/apps/mobile/components/text-ticker/index.ts b/apps/mobile/components/text-ticker/index.ts new file mode 100644 index 00000000..041e4717 --- /dev/null +++ b/apps/mobile/components/text-ticker/index.ts @@ -0,0 +1 @@ +export * from './text-ticker' diff --git a/apps/mobile/components/text-ticker/text-ticker.tsx b/apps/mobile/components/text-ticker/text-ticker.tsx new file mode 100644 index 00000000..aa9996d6 --- /dev/null +++ b/apps/mobile/components/text-ticker/text-ticker.tsx @@ -0,0 +1,122 @@ +import { cn } from '@/lib/utils' +import React, { useState } from 'react' +import type { TextStyle } from 'react-native' +import Animated, { + LinearTransition, + SlideInDown, + SlideOutDown, +} from 'react-native-reanimated' +import { Text } from '../ui/text' + +function formatNumberWithCommas(formatter: Intl.NumberFormat, num: number) { + const formattedNum = formatter.format(num) + const result: { value: string; key: string }[] = [] + let commaCount = 0 + + for (let i = 0; i < formattedNum.length; i++) { + const char = formattedNum[i] + // We want to count the number of commas because we would like to + // keep the index of the digits the same. + if (char === ',') { + result.push({ value: char, key: `comma-${i}` }) + + commaCount++ + } else { + result.push({ value: char, key: `digit-${i - commaCount}` }) + } + } + + return result +} + +type TextTickerProps = { + style?: TextStyle + className?: string + onChangeText?: (text: string) => void + value: string | number + formatter?: Intl.NumberFormat + autoFocus?: boolean + suffix?: string + suffixClassName?: string +} + +export function TextTicker({ + style, + className, + value = '0', + formatter = new Intl.NumberFormat('en-US'), + suffix, + suffixClassName, +}: TextTickerProps) { + const initialFontSize = style?.fontSize ?? 68 + const animationDuration = 300 + const [fontSize, setFontSize] = useState(initialFontSize) + + const formattedNumbers = React.useMemo(() => { + return formatNumberWithCommas(formatter, parseFloat(String(value) || '0')) + }, [value, formatter]) + + return ( + + {/* Using a dummy Text to let React Native do the math for the font size, + in case the text will not fit on a single line. */} + { + setFontSize(Math.round(e.nativeEvent.lines[0].ascender)) + }} + > + {formattedNumbers.map((x) => x.value).join('')} + {suffix} + + + {formattedNumbers.map((formattedNumber) => { + return ( + + {formattedNumber.value} + + ) + })} + {!!suffix && ( + + {suffix} + + )} + + + ) +}