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/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 d9c3f6d41f..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,7 +110,9 @@ export const Button = forwardRef< alignItems: 'center', ...sizeStyles[size] }}> - {leftIcon + {React.isValidElement(leftIcon) + ? leftIcon + : isButtonIconType(leftIcon) ? getIcon(leftIcon, { width: iconWidth, height: iconWidth, @@ -122,7 +130,9 @@ export const Button = forwardRef< }}> {children} - {rightIcon + {React.isValidElement(rightIcon) + ? rightIcon + : isButtonIconType(rightIcon) ? getIcon(rightIcon, { width: iconWidth, height: iconWidth, @@ -202,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 ( + + ) +} 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/Header/BalanceHeader.tsx b/packages/k2-alpine/src/components/Header/BalanceHeader.tsx index e463c30899..123621b2d6 100644 --- a/packages/k2-alpine/src/components/Header/BalanceHeader.tsx +++ b/packages/k2-alpine/src/components/Header/BalanceHeader.tsx @@ -29,7 +29,7 @@ export const BalanceHeader = ({ {accountName} 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,