diff --git a/packages/core-mobile/app/new/common/components/BlurredBackgroundView.tsx b/packages/core-mobile/app/new/common/components/BlurredBackgroundView.tsx
index 7b87d1045a..f1c0a784cf 100644
--- a/packages/core-mobile/app/new/common/components/BlurredBackgroundView.tsx
+++ b/packages/core-mobile/app/new/common/components/BlurredBackgroundView.tsx
@@ -1,6 +1,7 @@
import React from 'react'
-import { GlassView, Separator, View } from '@avalabs/k2-alpine'
+import { alpha, Separator, useTheme, View } from '@avalabs/k2-alpine'
import { Platform } from 'react-native'
+import { BlurView } from 'expo-blur'
const BlurredBackgroundView = ({
separator
@@ -10,13 +11,23 @@ const BlurredBackgroundView = ({
position: 'top' | 'bottom'
}
}): JSX.Element => {
+ const { theme } = useTheme()
+
return (
{separator?.position === 'top' && (
)}
{Platform.OS === 'ios' ? (
-
+
) : (
)}
diff --git a/packages/core-mobile/app/new/common/components/SimpleTextInput.tsx b/packages/core-mobile/app/new/common/components/SimpleTextInput.tsx
index 87bcc196ea..45644f8ab9 100644
--- a/packages/core-mobile/app/new/common/components/SimpleTextInput.tsx
+++ b/packages/core-mobile/app/new/common/components/SimpleTextInput.tsx
@@ -10,11 +10,13 @@ import {
export const SimpleTextInput = ({
name,
setName,
- placeholder
+ placeholder,
+ maxLength
}: {
name: string
setName: (name: string) => void
placeholder?: string
+ maxLength?: number
}): React.JSX.Element => {
const {
theme: { colors }
@@ -23,7 +25,7 @@ export const SimpleTextInput = ({
{name.length !== 0 && (
setName('')}>
diff --git a/packages/core-mobile/app/new/common/consts/screenOptions.tsx b/packages/core-mobile/app/new/common/consts/screenOptions.tsx
index ab666c5c3a..f0c460097f 100644
--- a/packages/core-mobile/app/new/common/consts/screenOptions.tsx
+++ b/packages/core-mobile/app/new/common/consts/screenOptions.tsx
@@ -112,7 +112,7 @@ export const homeScreenOptions: StackNavigationOptions = {
height: '100%',
justifyContent: 'center'
}}>
-
+
diff --git a/packages/core-mobile/app/new/common/hooks/useAnimatedNavigationHeader.tsx b/packages/core-mobile/app/new/common/hooks/useFadingHeaderNavigation.tsx
similarity index 52%
rename from packages/core-mobile/app/new/common/hooks/useAnimatedNavigationHeader.tsx
rename to packages/core-mobile/app/new/common/hooks/useFadingHeaderNavigation.tsx
index f98e9af2bd..20aca3b1b9 100644
--- a/packages/core-mobile/app/new/common/hooks/useAnimatedNavigationHeader.tsx
+++ b/packages/core-mobile/app/new/common/hooks/useFadingHeaderNavigation.tsx
@@ -1,38 +1,62 @@
import BlurredBackgroundView from 'common/components/BlurredBackgroundView'
import React, { useCallback, useEffect, useState } from 'react'
import { View } from '@avalabs/k2-alpine'
-import { LayoutChangeEvent, LayoutRectangle } from 'react-native'
+import {
+ LayoutChangeEvent,
+ LayoutRectangle,
+ NativeScrollEvent,
+ NativeSyntheticEvent,
+ ScrollViewProps
+} from 'react-native'
import { useNavigation } from 'expo-router'
+import { clamp } from 'react-native-reanimated'
-export const useAnimatedNavigationHeader = ({
+export const useFadingHeaderNavigation = ({
header,
- visibilityProgress
+ targetLayout
}: {
- header: JSX.Element
- visibilityProgress: number
-}): void => {
+ header?: JSX.Element
+ targetLayout?: LayoutRectangle
+}): Partial => {
const navigation = useNavigation()
const [navigationHeaderLayout, setNavigationHeaderLayout] = useState<
LayoutRectangle | undefined
>(undefined)
+ const [targetHiddenProgress, setTargetHiddenProgress] = useState(0) // from 0 to 1, 0 = fully hidden, 1 = fully shown
const renderHeaderBackground = useCallback(
() => (
),
- [visibilityProgress]
+ [targetHiddenProgress]
)
const handleLayout = (event: LayoutChangeEvent): void => {
setNavigationHeaderLayout(event.nativeEvent.layout)
}
+ const handleScroll = (
+ event: NativeSyntheticEvent
+ ): void => {
+ if (targetLayout) {
+ setTargetHiddenProgress(
+ // calculate balance header's visibility based on the scroll position
+ clamp(
+ event.nativeEvent.contentOffset.y /
+ (targetLayout.y + targetLayout.height),
+ 0,
+ 1
+ )
+ )
+ }
+ }
+
useEffect(() => {
navigation.setOptions({
headerBackground: renderHeaderBackground,
- title: (
+ title: header && (
)}
-
+
@@ -51,7 +56,10 @@ export const Confirmation = ({
+ sx={{
+ textAlign: 'center',
+ marginTop: 20
+ }}>
You can now start buying, swapping, sending, receiving crypto and
collectibles with no added fees
diff --git a/packages/core-mobile/app/new/features/onboarding/components/CreatePin.tsx b/packages/core-mobile/app/new/features/onboarding/components/CreatePin.tsx
index 76aeb7d11b..4599dd68ec 100644
--- a/packages/core-mobile/app/new/features/onboarding/components/CreatePin.tsx
+++ b/packages/core-mobile/app/new/features/onboarding/components/CreatePin.tsx
@@ -60,7 +60,12 @@ export const CreatePin = ({
-
+
-
- {!chosenPinEntered && (
+ {!chosenPinEntered && (
+
- )}
-
+
+ )}
diff --git a/packages/core-mobile/app/new/features/onboarding/components/EnterRecoveryPhrase.tsx b/packages/core-mobile/app/new/features/onboarding/components/EnterRecoveryPhrase.tsx
index 3fdfd9a85f..5dea612dbd 100644
--- a/packages/core-mobile/app/new/features/onboarding/components/EnterRecoveryPhrase.tsx
+++ b/packages/core-mobile/app/new/features/onboarding/components/EnterRecoveryPhrase.tsx
@@ -85,7 +85,10 @@ export const EnterRecoveryPhrase = ({
size="large"
type="primary"
onPress={handleNext}
- disabled={!mnemonic}>
+ disabled={
+ !mnemonic ||
+ mnemonic.trim().split(/\s+/).length < MINIMUM_MNEMONIC_WORDS
+ }>
Import
@@ -93,3 +96,5 @@ export const EnterRecoveryPhrase = ({
)
}
+
+const MINIMUM_MNEMONIC_WORDS = 12
diff --git a/packages/core-mobile/app/new/features/onboarding/components/RecoveryPhrase.tsx b/packages/core-mobile/app/new/features/onboarding/components/RecoveryPhrase.tsx
index e6bf90e099..e9b4335539 100644
--- a/packages/core-mobile/app/new/features/onboarding/components/RecoveryPhrase.tsx
+++ b/packages/core-mobile/app/new/features/onboarding/components/RecoveryPhrase.tsx
@@ -26,7 +26,7 @@ export const RecoveryPhrase = ({
function handleNext(): void {
showAlert({
- title: 'Security Warning',
+ title: 'Security warning',
description:
'For your security, you should not screenshot your recovery phrase. It is best to write it down and store it in a secure location.',
buttons: [
diff --git a/packages/core-mobile/app/new/features/onboarding/components/RecoveryPhraseInput.tsx b/packages/core-mobile/app/new/features/onboarding/components/RecoveryPhraseInput.tsx
index 55f5232130..5704f68a9f 100644
--- a/packages/core-mobile/app/new/features/onboarding/components/RecoveryPhraseInput.tsx
+++ b/packages/core-mobile/app/new/features/onboarding/components/RecoveryPhraseInput.tsx
@@ -149,7 +149,8 @@ export default function RecoveryPhraseInput({
sx={{
backgroundColor: '$surfaceSecondary',
borderRadius: 12,
- padding: 16,
+ paddingVertical: 12,
+ paddingHorizontal: 16,
minHeight: 150
}}>
)}
diff --git a/packages/core-mobile/app/new/features/onboarding/components/SetWalletName.tsx b/packages/core-mobile/app/new/features/onboarding/components/SetWalletName.tsx
index e00484f2e8..b8e3a35068 100644
--- a/packages/core-mobile/app/new/features/onboarding/components/SetWalletName.tsx
+++ b/packages/core-mobile/app/new/features/onboarding/components/SetWalletName.tsx
@@ -18,7 +18,11 @@ export const SetWalletName = ({
-
+
{
const { bottom } = useSafeAreaInsets()
const { theme } = useTheme()
+ const [headerLayout, setHeaderLayout] = useState<
+ LayoutRectangle | undefined
+ >()
+
+ const handleHeaderLayout = (event: LayoutChangeEvent): void => {
+ setHeaderLayout(event.nativeEvent.layout)
+ }
+
+ const scrollViewProps = useFadingHeaderNavigation({
+ targetLayout: headerLayout
+ })
return (
-
+
Terms and conditions
diff --git a/packages/core-mobile/app/new/routes/(signedIn)/(tabs)/portfolio/index.tsx b/packages/core-mobile/app/new/routes/(signedIn)/(tabs)/portfolio/index.tsx
index 91c65a64c8..89e14b1f61 100644
--- a/packages/core-mobile/app/new/routes/(signedIn)/(tabs)/portfolio/index.tsx
+++ b/packages/core-mobile/app/new/routes/(signedIn)/(tabs)/portfolio/index.tsx
@@ -9,18 +9,12 @@ import {
import { Link } from 'expo-router'
import BlurredBarsContentLayout from 'common/components/BlurredBarsContentLayout'
import { copyToClipboard } from 'common/utils/clipboard'
-import {
- LayoutChangeEvent,
- LayoutRectangle,
- NativeScrollEvent,
- NativeSyntheticEvent
-} from 'react-native'
-import { useAnimatedNavigationHeader } from 'common/hooks/useAnimatedNavigationHeader'
-import { clamp } from 'react-native-reanimated'
+import { LayoutChangeEvent, LayoutRectangle } from 'react-native'
+import { useFadingHeaderNavigation } from 'common/hooks/useFadingHeaderNavigation'
import { useApplicationContext } from 'contexts/ApplicationContext'
const PortfolioHomeScreen = (): JSX.Element => {
- const accountName = 'Account 1'
+ const accountName = 'Very long wallet name blar blar'
const formattedBalance = '$7,377.37'
const {
appHook: { selectedCurrency }
@@ -28,38 +22,19 @@ const PortfolioHomeScreen = (): JSX.Element => {
const [balanceHeaderLayout, setBalanceHeaderLayout] = useState<
LayoutRectangle | undefined
>()
- const [balanceHeaderHiddenProgress, setBalanceHeaderHiddenProgress] =
- useState(0) // from 0 to 1, 0 = fully hidden, 1 = fully shown
-
const handleCopyToClipboard = (): void => {
copyToClipboard('test')
}
- const handleScroll = (
- event: NativeSyntheticEvent
- ): void => {
- if (balanceHeaderLayout) {
- setBalanceHeaderHiddenProgress(
- // calculate balance header's visibility based on the scroll position
- clamp(
- event.nativeEvent.contentOffset.y /
- (balanceHeaderLayout.y + balanceHeaderLayout.height),
- 0,
- 1
- )
- )
- }
- }
-
const handleBalanceHeaderLayout = (event: LayoutChangeEvent): void => {
setBalanceHeaderLayout(event.nativeEvent.layout)
}
- useAnimatedNavigationHeader({
- visibilityProgress: balanceHeaderHiddenProgress,
+ const scrollViewProps = useFadingHeaderNavigation({
header: (
- )
+ ),
+ targetLayout: balanceHeaderLayout
})
return (
@@ -71,8 +46,7 @@ const PortfolioHomeScreen = (): JSX.Element => {
paddingHorizontal: 16,
gap: 16
}}
- scrollEventThrottle={16}
- onScroll={handleScroll}>
+ {...scrollViewProps}>
{
How would you like to access your existing wallet?
- }
- onPress={handleEnterRecoveryPhrase}
- />
-
- }
- onPress={handleCreateMnemonicWallet}
+ ,
+ onPress: handleEnterRecoveryPhrase
+ },
+ {
+ title: 'Create a new wallet',
+ leftIcon: (
+
+ ),
+ onPress: handleCreateMnemonicWallet
+ }
+ ]}
+ itemHeight={60}
/>
@@ -58,31 +60,4 @@ const AccessWalletScreen = (): JSX.Element => {
)
}
-const ActionButton = ({
- text,
- icon,
- onPress
-}: {
- text: string
- icon: JSX.Element
- onPress: () => void
-}): JSX.Element => {
- return (
-
-
- {icon}
- {text}
-
-
- )
-}
-
export default AccessWalletScreen
diff --git a/packages/core-mobile/app/new/routes/loginWithPinOrBiometry.tsx b/packages/core-mobile/app/new/routes/loginWithPinOrBiometry.tsx
index 37f8ef02dd..23dd6ae14c 100644
--- a/packages/core-mobile/app/new/routes/loginWithPinOrBiometry.tsx
+++ b/packages/core-mobile/app/new/routes/loginWithPinOrBiometry.tsx
@@ -94,15 +94,6 @@ const LoginWithPinOrBiometry = (): JSX.Element => {
}
})
- const avatarContainerMarginTop = useSharedValue(
- configuration.avatarContainerMarginTop.from
- )
- const avatarContainerStyle = useAnimatedStyle(() => {
- return {
- marginTop: avatarContainerMarginTop.value
- }
- })
-
const buttonContainerPaddingBottom = useSharedValue(
configuration.buttonContainerPaddingBottom.from
)
@@ -207,9 +198,6 @@ const LoginWithPinOrBiometry = (): JSX.Element => {
const buttonContainerPaddingBottomValue = isEnteringPin
? configuration.buttonContainerPaddingBottom.to
: configuration.buttonContainerPaddingBottom.from
- const avatarContainerMarginTopValue = isEnteringPin
- ? configuration.avatarContainerMarginTop.to
- : configuration.avatarContainerMarginTop.from
pinInputOpacity.value = withTiming(pinInputOpacityValue, {
duration: configuration.animationDuration
@@ -220,15 +208,7 @@ const LoginWithPinOrBiometry = (): JSX.Element => {
duration: configuration.animationDuration
}
)
- avatarContainerMarginTop.value = withTiming(avatarContainerMarginTopValue, {
- duration: configuration.animationDuration
- })
- }, [
- isEnteringPin,
- pinInputOpacity,
- buttonContainerPaddingBottom,
- avatarContainerMarginTop
- ])
+ }, [isEnteringPin, pinInputOpacity, buttonContainerPaddingBottom])
return (
@@ -259,7 +239,7 @@ const LoginWithPinOrBiometry = (): JSX.Element => {
)}
-
+
- Sign in with Google
+ Continue with Google
)}
{shouldShowApple && (
@@ -147,7 +147,7 @@ export default function Signup(): JSX.Element {
disabled={isRegistering}
leftIcon="apple"
onPress={handleAppleSignin}>
- Sign in with Apple
+ Continue with Apple
)}
diff --git a/packages/k2-alpine/package.json b/packages/k2-alpine/package.json
index 42a532cca1..fe0b3cadfb 100644
--- a/packages/k2-alpine/package.json
+++ b/packages/k2-alpine/package.json
@@ -35,6 +35,7 @@
},
"peerDependencies": {
"@react-native-masked-view/masked-view": "0.3.0",
+ "expo-blur": "12.9.2",
"expo-image": "1.10.6",
"expo-linear-gradient": "12.7.2",
"react": "18.3.1",
diff --git a/packages/k2-alpine/src/assets/icons/arrow_down.svg b/packages/k2-alpine/src/assets/icons/arrow_down.svg
new file mode 100644
index 0000000000..00baead390
--- /dev/null
+++ b/packages/k2-alpine/src/assets/icons/arrow_down.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/k2-alpine/src/components/Avatar/Avatar.tsx b/packages/k2-alpine/src/components/Avatar/Avatar.tsx
index 5a634af91e..06d209a0d6 100644
--- a/packages/k2-alpine/src/components/Avatar/Avatar.tsx
+++ b/packages/k2-alpine/src/components/Avatar/Avatar.tsx
@@ -45,7 +45,7 @@ export const Avatar = ({
const surfacePrimaryBlurBgMap = theme.isDark
? {
[theme.colors.$surfacePrimary]:
- Platform.OS === 'ios' ? '#050506' : '#0a0a0b',
+ Platform.OS === 'ios' ? '#37373f' : '#373743',
[theme.colors.$surfaceSecondary]:
Platform.OS === 'ios' ? '#37373f' : '#373743',
[theme.colors.$surfaceTertiary]:
diff --git a/packages/k2-alpine/src/components/Avatar/AvatarSelector.tsx b/packages/k2-alpine/src/components/Avatar/AvatarSelector.tsx
index 1ae5d1ac58..437887aad5 100644
--- a/packages/k2-alpine/src/components/Avatar/AvatarSelector.tsx
+++ b/packages/k2-alpine/src/components/Avatar/AvatarSelector.tsx
@@ -2,6 +2,7 @@ import { Dimensions, ImageSourcePropType } from 'react-native'
import React, { useMemo, useState } from 'react'
import Carousel from 'react-native-reanimated-carousel'
import { Pressable } from '../Primitives'
+import { isScreenSmall } from '../../utils'
import { Avatar } from './Avatar'
export const AvatarSelector = ({
@@ -22,6 +23,9 @@ export const AvatarSelector = ({
}
}, [avatars])
const [pressedIndex, setPressedIndex] = useState()
+ const avatarWidth = isScreenSmall
+ ? configuration.avatarWidth.small
+ : configuration.avatarWidth.large
const handlePressIn = (index: number): void => {
setPressedIndex(index)
@@ -51,13 +55,13 @@ export const AvatarSelector = ({
return (
handlePressIn(index)}
onPressOut={() => handlePressOut(index)}
onPress={() => handleSelect(index)}>
)
}
const configuration = {
- avatarWidth: 90,
+ avatarWidth: {
+ large: 90,
+ small: 60
+ },
spacing: 6
}
diff --git a/packages/k2-alpine/src/components/Avatar/HexagonImageView.tsx b/packages/k2-alpine/src/components/Avatar/HexagonImageView.tsx
index b9bbe4b17f..81b59939e2 100644
--- a/packages/k2-alpine/src/components/Avatar/HexagonImageView.tsx
+++ b/packages/k2-alpine/src/components/Avatar/HexagonImageView.tsx
@@ -119,7 +119,7 @@ export const HexagonBorder = ({ height }: { height: number }): JSX.Element => {
fill="none"
stroke={theme.isDark ? colors.$neutralWhite : 'black'}
strokeOpacity={0.1}
- strokeWidth="1"
+ strokeWidth="2"
/>
)
diff --git a/packages/k2-alpine/src/components/Button/Button.stories.tsx b/packages/k2-alpine/src/components/Button/Button.stories.tsx
index b7f11f485d..96e5aaf6dd 100644
--- a/packages/k2-alpine/src/components/Button/Button.stories.tsx
+++ b/packages/k2-alpine/src/components/Button/Button.stories.tsx
@@ -5,6 +5,7 @@ import { ScrollView, Text, View } from '../Primitives'
import Link from '../../utils/Link'
import { useTheme } from '../..'
import { Button, ButtonSize, ButtonType } from './Button'
+import { FilterButton } from './FilterButton'
export default {
title: 'Button'
@@ -103,6 +104,13 @@ export const All = (): JSX.Element => {
}}>
disabled
+
+
+
diff --git a/packages/k2-alpine/src/components/Button/Button.tsx b/packages/k2-alpine/src/components/Button/Button.tsx
index 3706a90960..3e30e6a8e8 100644
--- a/packages/k2-alpine/src/components/Button/Button.tsx
+++ b/packages/k2-alpine/src/components/Button/Button.tsx
@@ -20,6 +20,12 @@ export type ButtonSize = 'small' | 'medium' | 'large'
type ButtonIconType = 'check' | 'expandMore' | 'google' | 'apple'
+const BUTTON_ICON_TYPES = ['check', 'expandMore', 'google', 'apple'] as const
+
+const isButtonIconType = (value: unknown): value is ButtonIconType => {
+ return (BUTTON_ICON_TYPES as readonly string[]).includes(value as string)
+}
+
interface ButtonProps {
onPress?: () => void
disabled?: boolean
@@ -27,8 +33,8 @@ interface ButtonProps {
testID?: string
type: ButtonType
size: ButtonSize
- leftIcon?: ButtonIconType
- rightIcon?: ButtonIconType
+ leftIcon?: ButtonIconType | JSX.Element
+ rightIcon?: ButtonIconType | JSX.Element
}
export const Button = forwardRef<
@@ -104,16 +110,16 @@ export const Button = forwardRef<
alignItems: 'center',
...sizeStyles[size]
}}>
- {leftIcon ? (
- getIcon(leftIcon, {
- width: iconWidth,
- height: iconWidth,
- color: tintColor,
- style: { marginRight: 8 }
- })
- ) : rightIcon ? (
-
- ) : null}
+ {React.isValidElement(leftIcon)
+ ? leftIcon
+ : isButtonIconType(leftIcon)
+ ? getIcon(leftIcon, {
+ width: iconWidth,
+ height: iconWidth,
+ color: tintColor,
+ style: { marginRight: 8 }
+ })
+ : null}
{children}
- {rightIcon ? (
- getIcon(rightIcon, {
- width: iconWidth,
- height: iconWidth,
- color: tintColor,
- style: { marginLeft: 8 }
- })
- ) : leftIcon ? (
-
- ) : null}
+ {React.isValidElement(rightIcon)
+ ? rightIcon
+ : isButtonIconType(rightIcon)
+ ? getIcon(rightIcon, {
+ width: iconWidth,
+ height: iconWidth,
+ color: tintColor,
+ style: { marginLeft: 8 }
+ })
+ : null}
@@ -189,7 +195,7 @@ const getBackgroundColor = (
darkModeColors.$surfacePrimary
)
: overlayColor(
- alpha(darkModeColors.$surfaceSecondary, 0.3),
+ alpha(darkModeColors.$surfacePrimary, 0.3),
lightModeColors.$surfacePrimary
)
}
@@ -206,7 +212,7 @@ const getBackgroundColor = (
}
}
-const getTintColor = (
+export const getTintColor = (
type: ButtonType,
theme: K2AlpineTheme,
disabled: boolean | undefined
diff --git a/packages/k2-alpine/src/components/Button/FilterButton.tsx b/packages/k2-alpine/src/components/Button/FilterButton.tsx
new file mode 100644
index 0000000000..c7eb2f3ccc
--- /dev/null
+++ b/packages/k2-alpine/src/components/Button/FilterButton.tsx
@@ -0,0 +1,33 @@
+import React from 'react'
+import { ViewStyle } from 'react-native'
+import { Icons } from '../../theme/tokens/Icons'
+import { useTheme } from '../../hooks'
+import { Button, getTintColor } from './Button'
+
+export const FilterButton = ({
+ title,
+ style,
+ disabled,
+ onPress
+}: {
+ title: string
+ style?: ViewStyle
+ disabled?: boolean
+ onPress?: () => void
+}): JSX.Element => {
+ const { theme } = useTheme()
+ const tintColor = getTintColor('secondary', theme, disabled)
+
+ return (
+
+ }>
+ {title}
+
+ )
+}
diff --git a/packages/k2-alpine/src/components/Dropdown/DropdownBackground.tsx b/packages/k2-alpine/src/components/Dropdown/DropdownBackground.tsx
new file mode 100644
index 0000000000..386a259a04
--- /dev/null
+++ b/packages/k2-alpine/src/components/Dropdown/DropdownBackground.tsx
@@ -0,0 +1,35 @@
+import { BlurView } from 'expo-blur'
+import React from 'react'
+import { Platform } from 'react-native'
+import { View } from '../Primitives'
+import { useTheme } from '../../hooks'
+import { colors } from '../../theme/tokens/colors'
+
+export const DropdownBackground = ({
+ children
+}: {
+ children: React.ReactNode
+}): JSX.Element => {
+ const { theme } = useTheme()
+
+ return Platform.OS === 'ios' ? (
+
+ {children}
+
+ ) : (
+
+ {children}
+
+ )
+}
diff --git a/packages/k2-alpine/src/components/Dropdown/SimpleDropdown.stories.tsx b/packages/k2-alpine/src/components/Dropdown/SimpleDropdown.stories.tsx
index 77cb9754e2..e0bdd49415 100644
--- a/packages/k2-alpine/src/components/Dropdown/SimpleDropdown.stories.tsx
+++ b/packages/k2-alpine/src/components/Dropdown/SimpleDropdown.stories.tsx
@@ -1,8 +1,11 @@
-import React, { useState } from 'react'
+import React, { useRef, useState } from 'react'
+import { TouchableOpacity } from 'react-native'
+import { Rect } from 'react-native-popover-view'
import { ScrollView, Text, View } from '../Primitives'
import { Button } from '../Button/Button'
import { showAlert } from '../Alert/Alert'
import { IndexPath, SimpleDropdown } from './SimpleDropdown'
+import { usePopoverAnchor } from './usePopoverAnchor'
export default {
title: 'Dropdown'
@@ -20,6 +23,8 @@ export const All = (): JSX.Element => {
+
+
)
@@ -178,3 +183,89 @@ const MultipleSectionMultipleSelectionDropdown = (): JSX.Element => {
)
}
+
+const LeftAlignedDropdown = (): JSX.Element => {
+ const sections = [
+ ['All networks', 'Avalanche C-Chain', 'Bitcoin network', 'Ethereum']
+ ]
+ const [selectedRow, setSelectedRow] = useState({
+ section: 0,
+ row: 0
+ })
+ const sourceRef = useRef(null)
+
+ const { anchorRect, isPopoverVisible, onShowPopover, onHidePopover } =
+ usePopoverAnchor(sourceRef)
+
+ return (
+
+ Left aligned
+
+ setSelectedRow(indexPath)}
+ onRequestClose={onHidePopover}
+ />
+
+ )
+}
+
+const RightAlignedDropdown = (): JSX.Element => {
+ const sections = [
+ ['All networks', 'Avalanche C-Chain', 'Bitcoin network', 'Ethereum']
+ ]
+ const [selectedRow, setSelectedRow] = useState({
+ section: 0,
+ row: 0
+ })
+ const sourceRef = useRef(null)
+
+ const { anchorRect, isPopoverVisible, onShowPopover, onHidePopover } =
+ usePopoverAnchor(sourceRef)
+
+ return (
+
+ Right aligned
+
+ setSelectedRow(indexPath)}
+ onRequestClose={onHidePopover}
+ />
+
+ )
+}
diff --git a/packages/k2-alpine/src/components/Dropdown/SimpleDropdown.tsx b/packages/k2-alpine/src/components/Dropdown/SimpleDropdown.tsx
index a64686d297..8f1cb5c2c4 100644
--- a/packages/k2-alpine/src/components/Dropdown/SimpleDropdown.tsx
+++ b/packages/k2-alpine/src/components/Dropdown/SimpleDropdown.tsx
@@ -1,10 +1,11 @@
import React, { useRef } from 'react'
-import Popover from 'react-native-popover-view'
-import { BlurView } from 'expo-blur'
+import Popover, { Rect } from 'react-native-popover-view'
+import { Platform } from 'react-native'
import { Text, View, TouchableOpacity } from '../Primitives'
import { useTheme } from '../../hooks'
import { Separator } from '../Separator/Separator'
import { Icons } from '../../theme/tokens/Icons'
+import { DropdownBackground } from './DropdownBackground'
export const SimpleDropdown = ({
from,
@@ -13,15 +14,19 @@ export const SimpleDropdown = ({
offset = 0,
allowsMultipleSelection = false,
onSelectRow,
- onDeselectRow
+ onDeselectRow,
+ onRequestClose,
+ isVisible
}: {
- from: React.ReactNode
+ from: React.ReactNode | Rect
sections: T[][]
selectedRows: IndexPath[]
allowsMultipleSelection?: boolean
offset?: number
onSelectRow: (indexPath: IndexPath) => void
onDeselectRow?: (indexPath: IndexPath) => void
+ onRequestClose?: () => void
+ isVisible?: boolean
}): JSX.Element => {
const { theme } = useTheme()
const popoverRef = useRef()
@@ -48,28 +53,28 @@ export const SimpleDropdown = ({
)
}
+ const groupSeparatorHeight = Platform.OS === 'ios' ? 6 : 1
+ const backgroundBorderRadius = Platform.OS === 'ios' ? 10 : 4
+
return (
-
+
{sections.map((section, sectionIndex) => {
return (
@@ -107,14 +112,15 @@ export const SimpleDropdown = ({
)}
- {rowIndex !== section.length - 1 && }
+ {rowIndex !== section.length - 1 &&
+ Platform.OS === 'ios' && }
)
})}
{sectionIndex !== sections.length - 1 && (
@@ -122,7 +128,7 @@ export const SimpleDropdown = ({
)
})}
-
+
)
}
diff --git a/packages/k2-alpine/src/components/Dropdown/usePopoverAnchor.ts b/packages/k2-alpine/src/components/Dropdown/usePopoverAnchor.ts
new file mode 100644
index 0000000000..9548c2cb24
--- /dev/null
+++ b/packages/k2-alpine/src/components/Dropdown/usePopoverAnchor.ts
@@ -0,0 +1,43 @@
+import { useEffect, useState } from 'react'
+import { NativeMethods } from 'react-native'
+import { Rect } from 'react-native-popover-view'
+
+export const usePopoverAnchor = (
+ sourceRef: React.RefObject
+): {
+ anchorRect: Rect | undefined
+ isPopoverVisible: boolean
+ onShowPopover: () => void
+ onHidePopover: () => void
+} => {
+ const [isPopoverVisible, setIsPopoverVisible] = useState(false)
+ const [anchorRect, setAnchorRect] = useState()
+
+ const showPopover = (): void => {
+ if (sourceRef.current) {
+ // eslint-disable-next-line max-params
+ sourceRef.current.measureInWindow((x, y, width, height) => {
+ setAnchorRect(new Rect(x, y, width, height))
+ })
+ }
+ }
+
+ const hidePopover = (): void => {
+ setAnchorRect(undefined)
+ }
+
+ useEffect(() => {
+ if (anchorRect) {
+ setIsPopoverVisible(true)
+ } else {
+ setIsPopoverVisible(false)
+ }
+ }, [anchorRect])
+
+ return {
+ anchorRect,
+ isPopoverVisible,
+ onShowPopover: showPopover,
+ onHidePopover: hidePopover
+ }
+}
diff --git a/packages/k2-alpine/src/components/GroupList/GroupList.stories.tsx b/packages/k2-alpine/src/components/GroupList/GroupList.stories.tsx
index 7f1f88d96b..2a41968473 100644
--- a/packages/k2-alpine/src/components/GroupList/GroupList.stories.tsx
+++ b/packages/k2-alpine/src/components/GroupList/GroupList.stories.tsx
@@ -1,7 +1,7 @@
import React, { useState } from 'react'
import { Switch } from 'react-native'
import { ScrollView, View } from '../Primitives'
-import { showAlert, useTheme } from '../..'
+import { Icons, showAlert, useTheme } from '../..'
import { GroupList } from './GroupList'
export default {
@@ -52,6 +52,25 @@ export const All = (): JSX.Element => {
}
]}
/>
+ ,
+ onPress: () => {
+ showAlert({ title: 'Pressed', buttons: [{ text: 'OK' }] })
+ }
+ },
+ {
+ title: 'Create a new wallet',
+ leftIcon: ,
+ onPress: () => {
+ showAlert({ title: 'Pressed', buttons: [{ text: 'OK' }] })
+ }
+ }
+ ]}
+ itemHeight={60}
+ />
)
diff --git a/packages/k2-alpine/src/components/GroupList/GroupList.tsx b/packages/k2-alpine/src/components/GroupList/GroupList.tsx
index 23e713784b..c0513fa35c 100644
--- a/packages/k2-alpine/src/components/GroupList/GroupList.tsx
+++ b/packages/k2-alpine/src/components/GroupList/GroupList.tsx
@@ -1,55 +1,84 @@
-import React from 'react'
-import { View, Text, TouchableHighlight } from '../Primitives'
+import React, { useState } from 'react'
+import { LayoutChangeEvent } from 'react-native'
+import { View, Text, TouchableOpacity } from '../Primitives'
import { Separator } from '../Separator/Separator'
import { Icons } from '../../theme/tokens/Icons'
import { useTheme } from '../../hooks'
-export const GroupList = ({ data }: { data: GroupListItem[] }): JSX.Element => {
+export const GroupList = ({
+ data,
+ itemHeight
+}: {
+ data: GroupListItem[]
+ itemHeight?: number
+}): JSX.Element => {
const { theme } = useTheme()
+ const [textMarginLeft, setTextMarginLeft] = useState(0)
+
+ const handleLayout = (event: LayoutChangeEvent): void => {
+ setTextMarginLeft(event.nativeEvent.layout.x)
+ }
return (
-
- {data.map(({ title, value, accessory, onPress }, index) => (
+
+ {data.map(({ leftIcon, title, value, accessory, onPress }, index) => (
-
+
-
- {title}
-
+ {leftIcon && {leftIcon}}
- {value !== undefined && (
-
- {value}
-
- )}
- {accessory !== undefined && accessory}
- {accessory === undefined && onPress !== undefined && (
-
- )}
+ justifyContent: 'space-between',
+ marginLeft: 15
+ }}
+ onLayout={handleLayout}>
+
+ {title}
+
+
+ {value !== undefined && (
+
+ {value}
+
+ )}
+ {accessory !== undefined && accessory}
+ {accessory === undefined && onPress !== undefined && (
+
+ )}
+
-
- {index < data.length - 1 && }
+
+ {index < data.length - 1 && (
+
+ )}
))}
@@ -60,5 +89,6 @@ export type GroupListItem = {
title: string
value?: string
onPress?: () => void
+ leftIcon?: JSX.Element
accessory?: JSX.Element
}
diff --git a/packages/k2-alpine/src/components/Header/BalanceHeader.tsx b/packages/k2-alpine/src/components/Header/BalanceHeader.tsx
index 5464c34b1c..123621b2d6 100644
--- a/packages/k2-alpine/src/components/Header/BalanceHeader.tsx
+++ b/packages/k2-alpine/src/components/Header/BalanceHeader.tsx
@@ -29,7 +29,8 @@ export const BalanceHeader = ({
+ sx={{ color: '$textSecondary', lineHeight: 38, marginRight: 100 }}
+ numberOfLines={1}>
{accountName}
diff --git a/packages/k2-alpine/src/components/Header/NavigationTitleHeader.tsx b/packages/k2-alpine/src/components/Header/NavigationTitleHeader.tsx
index 32298d2a2e..dcf0b051a2 100644
--- a/packages/k2-alpine/src/components/Header/NavigationTitleHeader.tsx
+++ b/packages/k2-alpine/src/components/Header/NavigationTitleHeader.tsx
@@ -6,11 +6,20 @@ export const NavigationTitleHeader = memo(
return (
+ numberOfLines={1}
+ sx={{
+ fontSize: 17,
+ lineHeight: 20,
+ fontFamily: 'Inter-SemiBold',
+ maxWidth: 150
+ }}>
{title}
{subtitle && (
-
+
{subtitle}
)}
diff --git a/packages/k2-alpine/src/components/TextInput/TextInput.tsx b/packages/k2-alpine/src/components/TextInput/TextInput.tsx
index 1efbec2294..0b935620bc 100644
--- a/packages/k2-alpine/src/components/TextInput/TextInput.tsx
+++ b/packages/k2-alpine/src/components/TextInput/TextInput.tsx
@@ -11,6 +11,7 @@ interface TextInputProps {
sx?: SxProp
rightIcon?: React.ReactNode
leftIcon?: React.ReactNode
+ maxLength?: number
}
export const TextInput = ({
@@ -20,7 +21,8 @@ export const TextInput = ({
placeholder,
onChangeText,
rightIcon,
- leftIcon
+ leftIcon,
+ maxLength
}: TextInputProps): React.JSX.Element => {
const {
theme: { colors }
@@ -29,7 +31,7 @@ export const TextInput = ({
return (
diff --git a/packages/k2-alpine/src/theme/tokens/Icons.ts b/packages/k2-alpine/src/theme/tokens/Icons.ts
index fd374eaf4b..f752d86236 100644
--- a/packages/k2-alpine/src/theme/tokens/Icons.ts
+++ b/packages/k2-alpine/src/theme/tokens/Icons.ts
@@ -33,6 +33,7 @@ import IconTrendingArrowDown from '../../assets/icons/trending_arrow_down.svg'
import IconQRCode2 from '../../assets/icons/qr_code_2.svg'
import IconNotifications from '../../assets/icons/notifications.svg'
import IconAlertError from '../../assets/icons/alert_error.svg'
+import IconArrowDown from '../../assets/icons/arrow_down.svg'
export const Icons = {
Action: {
@@ -74,7 +75,8 @@ export const Icons = {
Psychiatry: IconPsychiatry,
Error: IconError,
TrendingArrowUp: IconTrendingArrowUp,
- TrendingArrowDown: IconTrendingArrowDown
+ TrendingArrowDown: IconTrendingArrowDown,
+ ArrowDown: IconArrowDown
},
RecoveryMethod: {
Passkey: IconPasskey,
diff --git a/packages/k2-alpine/src/theme/tokens/colors.ts b/packages/k2-alpine/src/theme/tokens/colors.ts
index ddce9fc570..3751cd3158 100644
--- a/packages/k2-alpine/src/theme/tokens/colors.ts
+++ b/packages/k2-alpine/src/theme/tokens/colors.ts
@@ -44,8 +44,8 @@ export const darkModeColors = {
$textSuccess: colors.$accentSuccessD,
// surface
- $surfacePrimary: colors.$neutral950,
- $surfaceSecondary: colors.$neutral850,
+ $surfacePrimary: colors.$neutral850,
+ $surfaceSecondary: alpha(colors.$neutralWhite, 0.1),
$surfaceTertiary: colors.$neutral900,
// border
diff --git a/packages/k2-alpine/src/utils/index.ts b/packages/k2-alpine/src/utils/index.ts
index 67b2602ea5..6d56fbcf6c 100644
--- a/packages/k2-alpine/src/utils/index.ts
+++ b/packages/k2-alpine/src/utils/index.ts
@@ -1 +1,2 @@
export * from './colors'
+export * from './screens'
diff --git a/packages/k2-alpine/src/utils/screens.ts b/packages/k2-alpine/src/utils/screens.ts
new file mode 100644
index 0000000000..f4659ef808
--- /dev/null
+++ b/packages/k2-alpine/src/utils/screens.ts
@@ -0,0 +1,5 @@
+import { Dimensions } from 'react-native'
+
+const SCREEN_HEIGHT = Dimensions.get('window').height
+
+export const isScreenSmall = SCREEN_HEIGHT <= 670
diff --git a/yarn.lock b/yarn.lock
index 2b34f31f2c..7da1d02cbd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -559,6 +559,7 @@ __metadata:
typescript: 5.6.3
peerDependencies:
"@react-native-masked-view/masked-view": 0.3.0
+ expo-blur: 12.9.2
expo-image: 1.10.6
expo-linear-gradient: 12.7.2
react: 18.3.1
@@ -27107,7 +27108,7 @@ react-native-webview@ava-labs/react-native-webview:
peerDependencies:
react: "*"
react-native: "*"
- checksum: 77324747a8b5df0a5558bb99a9a0804a5575d328e84f480e462c56417af97213f38a9930e4582fb749bec374dc4d1a8910a45b006e77af9b14a8e64057b932bf
+ checksum: 869028a5bb7a4a8a125d274753b703c2b3579b5efc7bb82db136a186fd88a11c5a4696505ebcd976d20fcdd41111abe229ae0e19c27c50bb30fe4f52bb6383bc
languageName: node
linkType: hard