Skip to content

Commit

Permalink
OK-18136 Sidebar collapse (#2715)
Browse files Browse the repository at this point in the history
* feat: collapsable sidebar

* fix: ipad animation
  • Loading branch information
franco-chan committed Mar 15, 2023
1 parent c924586 commit f10dca1
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 71 deletions.
150 changes: 112 additions & 38 deletions packages/components/src/Layout/NavigationBar/Desktop.tsx
@@ -1,8 +1,10 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */
import type { FC } from 'react';
import { useMemo } from 'react';
import { useMemo, useState } from 'react';

import { CommonActions } from '@react-navigation/native';
import { LinearGradient } from 'expo-linear-gradient';
import { AnimatePresence, MotiView } from 'moti';

import { useThemeValue } from '@onekeyhq/components';
import WalletSelectorTrigger from '@onekeyhq/kit/src/components/WalletSelector/WalletSelectorTrigger/WalletSelectorTrigger';
Expand All @@ -12,6 +14,7 @@ import Box from '../../Box';
import { DesktopDragZoneAbsoluteBar } from '../../DesktopDragZoneBox';
import Icon from '../../Icon';
import Pressable from '../../Pressable';
import useSafeAreaInsets from '../../Provider/hooks/useSafeAreaInsets';
import ScrollView from '../../ScrollView';
import Typography from '../../Typography';
import VStack from '../../VStack';
Expand All @@ -21,10 +24,21 @@ import type { BottomTabBarProps } from '../BottomTabs';

const Sidebar: FC<BottomTabBarProps> = ({ navigation, state, descriptors }) => {
const { routes } = state;
const [isCollpase, setIsCollapse] = useState(false);
const { top } = useSafeAreaInsets(); // used for ipad
const dragZoneAbsoluteBarHeight = platformEnv.isDesktopMac ? 20 : 0; // used for desktop
const paddingTopValue = 12 + top + dragZoneAbsoluteBarHeight;

const [activeFontColor, inactiveFontColor] = useThemeValue([
const [
sidebarBackgroundColor,
activeFontColor,
inactiveFontColor,
shadowColor,
] = useThemeValue([
'surface-subdued',
'text-default',
'text-subdued',
'interactive-default',
]);

const tabs = useMemo(
Expand Down Expand Up @@ -54,64 +68,89 @@ const Sidebar: FC<BottomTabBarProps> = ({ navigation, state, descriptors }) => {
<Pressable
key={route.name}
onPress={onPress}
_hover={!isActive ? { bg: 'surface-hovered' } : undefined}
bg={isActive ? 'surface-selected' : undefined}
borderRadius="xl"
flexDirection="row"
alignItems="center"
mt={index === routes.length - 1 ? 'auto' : undefined}
p="2"
p="8px"
borderRadius="xl"
bg={isActive ? 'surface-selected' : undefined}
_hover={!isActive ? { bg: 'surface-hovered' } : undefined}
aria-current={isActive ? 'page' : undefined}
>
<Box
aria-current={isActive ? 'page' : undefined}
display="flex"
flexDirection="column"
>
<Box display="flex" flexDirection="row" alignItems="center">
<Icon
// @ts-expect-error
name={options?.tabBarIcon?.() as ICON_NAMES}
color={isActive ? 'icon-default' : 'icon-subdued'}
size={24}
/>
<Box>
<Icon
// @ts-expect-error
name={options?.tabBarIcon?.() as ICON_NAMES}
color={isActive ? 'icon-default' : 'icon-subdued'}
size={24}
/>
</Box>

<Typography.Body2Strong
ml="3"
color={isActive ? activeFontColor : inactiveFontColor}
<AnimatePresence initial={false}>
{!isCollpase && (
<MotiView
from={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{
opacity: 0,
}}
transition={{
type: 'timing',
duration: 150,
}}
style={{
flex: 1,
flexDirection: 'row',
alignItems: 'center',
}}
>
{options.tabBarLabel ?? route.name}
</Typography.Body2Strong>
</Box>
</Box>
{/* In the future, perhaps a 'Badge' will be placed here. */}
<Typography.Body2Strong
flex={1}
ml="3"
color={isActive ? activeFontColor : inactiveFontColor}
isTruncated
>
{options.tabBarLabel ?? route.name}
</Typography.Body2Strong>
</MotiView>
)}
</AnimatePresence>
</Pressable>
);
}),
[
activeFontColor,
descriptors,
inactiveFontColor,
isCollpase,
navigation,
routes,
state.index,
state.key,
],
);

const paddingTopValue = 3 + (platformEnv.isDesktopMac ? 5 : 0);
return (
<Box
position="relative"
w="224px"
h="full"
bg="surface-subdued"
px={4}
pt={paddingTopValue}
pb={5}
<MotiView
animate={{ width: isCollpase ? 72 : 224 }}
transition={{
type: 'timing',
duration: 150,
}}
style={{
height: '100%',
width: 224,
backgroundColor: sidebarBackgroundColor,
paddingHorizontal: 16,
paddingTop: paddingTopValue,
paddingBottom: 20,
}}
>
<DesktopDragZoneAbsoluteBar h={paddingTopValue} />
<DesktopDragZoneAbsoluteBar h={dragZoneAbsoluteBarHeight} />
{/* Scrollable area */}
<Box zIndex={1} testID="Desktop-WalletSelector-Container">
{/* <AccountSelector /> */}
<WalletSelectorTrigger />
<WalletSelectorTrigger showWalletName={!isCollpase} />
</Box>
<VStack flex={1} mt={4} mb={2}>
<ScrollView
Expand All @@ -128,7 +167,42 @@ const Sidebar: FC<BottomTabBarProps> = ({ navigation, state, descriptors }) => {
{/* <ChainSelector /> */}
{/* <NetworkAccountSelectorTrigger /> */}
</Box>
</Box>
<Pressable
onPress={() => {
setIsCollapse(!isCollpase);
}}
position="absolute"
top="0"
bottom="0"
right="-8px"
w="16px"
>
{({ isHovered, isPressed }) => (
<MotiView
animate={{ opacity: isHovered || isPressed ? 1 : 0 }}
transition={{ type: 'timing', duration: 150 }}
style={{ height: '100%', flexDirection: 'row' }}
>
<LinearGradient
colors={['transparent', shadowColor]}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
style={{
flex: 1,
height: '100%',
opacity: 0.1,
}}
/>
<Box
h="full"
flex={1}
borderLeftWidth={1}
borderLeftColor="interactive-default"
/>
</MotiView>
)}
</Pressable>
</MotiView>
);
};
export default Sidebar;
Expand Up @@ -18,6 +18,8 @@ import WalletSelectorDesktop from '../WalletSelectorDesktop';

import { WalletSelectorTriggerElement } from './WalletSelectorTriggerElement';

import type { WalletSelectorTriggerElementProps } from './WalletSelectorTriggerElement';

type AccountSelectorProps = {
renderTrigger?: ({
visible,
Expand All @@ -30,7 +32,9 @@ type AccountSelectorProps = {

const { updateDesktopWalletSelectorVisible } = reducerAccountSelector.actions;

const WalletSelectorTrigger: FC<AccountSelectorProps> = ({ renderTrigger }) => {
const WalletSelectorTrigger: FC<
AccountSelectorProps & Partial<WalletSelectorTriggerElementProps>
> = ({ renderTrigger, showWalletName }) => {
const isVertical = useIsVerticalLayout();

const triggerRef = useRef<HTMLElement>(null);
Expand Down Expand Up @@ -89,13 +93,13 @@ const WalletSelectorTrigger: FC<AccountSelectorProps> = ({ renderTrigger }) => {
ref={triggerRef}
position="relative"
alignItems="flex-start"
h="56px"
justifyContent="center"
w="full"
>
{renderTrigger?.({ visible, handleToggleVisible }) ?? (
<WalletSelectorTriggerElement
visible={visible}
showWalletName={showWalletName}
handleToggleVisible={handleToggleVisibleDefault}
/>
)}
Expand Down
@@ -1,21 +1,19 @@
import type { FC } from 'react';

import { useNavigation } from '@react-navigation/native';
import { AnimatePresence, MotiView } from 'moti';
import { useIntl } from 'react-intl';
import { Rect } from 'react-native-svg';

import {
Box,
Button,
Hidden,
Icon,
Pressable,
Skeleton,
Typography,
useIsVerticalLayout,
useUserDevice,
} from '@onekeyhq/components';
import platformEnv from '@onekeyhq/shared/src/platformEnv';

import { useActiveWalletAccount, useAppSelector } from '../../../hooks/redux';
import { useWalletName } from '../../../hooks/useWalletName';
Expand All @@ -27,22 +25,20 @@ import type { CreateWalletRoutesParams } from '../../../routes';
import type { ModalScreenProps } from '../../../routes/types';

type NavigationProps = ModalScreenProps<CreateWalletRoutesParams>;
type Props = {
export type WalletSelectorTriggerElementProps = {
visible: boolean;
showWalletName?: boolean;
handleToggleVisible: () => void;
};

export const WalletSelectorTriggerElement: FC<Props> = ({
visible,
handleToggleVisible,
}) => {
export const WalletSelectorTriggerElement: FC<
WalletSelectorTriggerElementProps
> = ({ visible, showWalletName, handleToggleVisible }) => {
const intl = useIntl();
const isVerticalLayout = useIsVerticalLayout();
const { wallet } = useActiveWalletAccount();
const { screenWidth } = useUserDevice();
const navigation = useNavigation<NavigationProps['navigation']>();
const isLoading = useAppSelector((s) => s.accountSelector.isLoading);
const maxItemWidth = screenWidth / 2 - (platformEnv.isNative ? 72 : 0);
const walletName = useWalletName({ wallet });
const { devicesStatus } = useDeviceStatusOfHardwareWallet();

Expand All @@ -63,11 +59,7 @@ export const WalletSelectorTriggerElement: FC<Props> = ({
// ** Android will crash after account switch
// const showExternalImg = true;
return (
<Pressable
onPress={handleToggleVisible}
justifyContent="center"
hitSlop={8}
>
<Pressable onPress={handleToggleVisible} hitSlop={8} w="full">
{({ isHovered }) => (
<Box
flexDirection="row"
Expand All @@ -76,7 +68,6 @@ export const WalletSelectorTriggerElement: FC<Props> = ({
p={1}
pr={{ base: 1, md: 2 }}
borderRadius="12px"
maxW={`${maxItemWidth}px`}
bg={
// eslint-disable-next-line no-nested-ternary
visible && !isVerticalLayout
Expand All @@ -101,22 +92,37 @@ export const WalletSelectorTriggerElement: FC<Props> = ({
/>
)}
</Box>
<Hidden from="base" till="md">
<>
<Typography.Body2Strong
isTruncated
numberOfLines={1}
ml={3}
mr={1}
maxWidth="106px"
<AnimatePresence initial={false}>
{showWalletName && (
<MotiView
from={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{
opacity: 0,
}}
transition={{
type: 'timing',
duration: 150,
}}
style={{
flex: 1,
flexDirection: 'row',
alignItems: 'center',
}}
>
{name}
</Typography.Body2Strong>
<Box ml={!isVerticalLayout ? 'auto' : undefined}>
<Icon size={20} name="ChevronUpDownMini" color="icon-subdued" />
</Box>
</>
</Hidden>
<Typography.Body2Strong flex={1} isTruncated ml={3} mr={1}>
{name}
</Typography.Body2Strong>
<Box>
<Icon
size={20}
name="ChevronUpDownMini"
color="icon-subdued"
/>
</Box>
</MotiView>
)}
</AnimatePresence>
</Box>
)}
</Pressable>
Expand Down

0 comments on commit f10dca1

Please sign in to comment.