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..3543c84f 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..2120d5a2
--- /dev/null
+++ b/apps/mobile/components/numeric-pad/numeric-pad.tsx
@@ -0,0 +1,85 @@
+import { cn } from '@/lib/utils'
+import { DeleteIcon } from 'lucide-react-native'
+import { View } from 'react-native'
+import { useSafeAreaInsets } from 'react-native-safe-area-context'
+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
+ className?: string
+}
+
+export function NumericPad({
+ disabled,
+ value = 0,
+ onValueChange,
+ maxValue = 9999999999,
+ className,
+}: NumericPadProps) {
+ const { bottom } = useSafeAreaInsets()
+
+ 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..5d4f10ca
--- /dev/null
+++ b/apps/mobile/components/text-ticker/text-ticker.tsx
@@ -0,0 +1,129 @@
+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}
+
+
+ )}
+
+
+ )
+}