Skip to content

Commit

Permalink
Connect wallet button [interchain UI part 2] (#1014)
Browse files Browse the repository at this point in the history
* Add ibc in slice

* Connect wallet

* Update dropdown

* cleanup
  • Loading branch information
grod220 authored Apr 30, 2024
1 parent a59d9d6 commit d03bb8c
Show file tree
Hide file tree
Showing 22 changed files with 807 additions and 242 deletions.
15 changes: 8 additions & 7 deletions apps/minifront/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
"@bufbuild/protobuf": "^1.9.0",
"@cosmjs/proto-signing": "^0.32.3",
"@cosmjs/stargate": "^0.32.3",
"@cosmos-kit/react": "^2.11.0",
"@interchain-ui/react": "^1.23.3",
"@cosmos-kit/core": "^2.9.2",
"@cosmos-kit/react": "^2.11.2",
"@interchain-ui/react": "^1.23.10",
"@penumbra-labs/registry": "^5.1.0",
"@penumbra-zone/bech32m": "workspace:*",
"@penumbra-zone/client": "workspace:*",
Expand All @@ -36,14 +37,14 @@
"@tanstack/react-query": "^5.28.9",
"bech32": "^2.0.0",
"bignumber.js": "^9.1.2",
"chain-registry": "^1.41.9",
"cosmos-kit": "^2.10.0",
"chain-registry": "^1.45.5",
"cosmos-kit": "^2.10.2",
"date-fns": "^3.6.0",
"framer-motion": "^11.0.22",
"immer": "^10.0.4",
"lodash": "^4.17.21",
"match-sorter": "^6.3.4",
"osmo-query": "^16.11.0",
"lucide-react": "^0.363.0",
"osmo-query": "^16.12.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-helmet": "^6.1.0",
Expand All @@ -55,7 +56,7 @@
"zustand": "^4.5.2"
},
"devDependencies": {
"@chain-registry/types": "^0.25.7",
"@chain-registry/types": "^0.28.4",
"@penumbra-zone/polyfills": "workspace:*",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^14.2.2",
Expand Down
165 changes: 69 additions & 96 deletions apps/minifront/src/components/ibc/ibc-in/chain-dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import * as React from 'react';
import { useMemo } from 'react';
import { Avatar, Box, Combobox, Skeleton, Stack, Text } from '@interchain-ui/react';
import { matchSorter } from 'match-sorter';
import { useManager } from '@cosmos-kit/react';

interface Option {
label: string;
value: string;
}
import { Popover, PopoverContent, PopoverTrigger } from '@penumbra-zone/ui/components/ui/popover';
import { ChevronsUpDown } from 'lucide-react';
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
} from '@penumbra-zone/ui/components/ui/command';
import { Button } from '@penumbra-zone/ui/components/ui/button';
import { ibcInSelector } from '../../../state/ibc-in';
import { useStore } from '../../../state';
import { Avatar, AvatarImage } from '@penumbra-zone/ui/components/ui/avatar';
import { Identicon } from '@penumbra-zone/ui/components/ui/identicon';

export interface ChainInfo {
chainName: string;
Expand All @@ -16,26 +23,6 @@ export interface ChainInfo {
icon?: string;
}

export interface ChooseChainProps {
onChange: (selectedItem: Option) => void;
}

const ChainOption = ({ chainInfo: { label, icon } }: { chainInfo: ChainInfo }) => {
return (
<Stack aria-label={`Chain option: ${label}`}>
<Avatar
name={label}
className='mr-2'
getInitials={name => name[0]!}
size='xs'
src={icon}
fallbackMode='bg'
/>
<Text>{label}</Text>
</Stack>
);
};

const useChainInfos = (): ChainInfo[] => {
const { chainRecords, getChainLogo } = useManager();
return useMemo(
Expand All @@ -54,80 +41,66 @@ const useChainInfos = (): ChainInfo[] => {

// Note the console will display aria-label warnings (despite them being present).
// The cosmology team has been notified of the issue.
export const ChainDropdown = ({ onChange }: ChooseChainProps) => {
export const ChainDropdown = () => {
const chainInfos = useChainInfos();
const { setSelectedChain } = useStore(ibcInSelector);

const [selectedKey, setSelectedKey] = React.useState<string>();
const [filterValue, setFilterValue] = React.useState<string>('');

const filteredItems = React.useMemo(() => {
return matchSorter(chainInfos, filterValue, {
keys: ['label', 'value'],
});
}, [chainInfos, filterValue]);
const [open, setOpen] = React.useState(false);
const [value, setValue] = React.useState('');

const avatarUrl = filteredItems.find(i => i.value === selectedKey)?.icon ?? undefined;
const selected = chainInfos.find(c => c.value === value);

return (
<Box className='flex flex-col items-center justify-center'>
<div className='font-bold text-stone-700'>Select chain</div>
<Combobox
aria-label='Select a chain'
items={filteredItems}
inputValue={filterValue}
openOnFocus
onInputChange={value => {
setFilterValue(value);
if (!value) {
setSelectedKey(undefined);
}
}}
selectedKey={selectedKey}
onSelectionChange={item => {
if (item) {
setSelectedKey(item as string);

const found = chainInfos.find(options => options.value === item) ?? null;

if (found) {
onChange(found);
setFilterValue(found.label);
}
}
}}
inputAddonStart={
selectedKey && avatarUrl ? (
<Avatar
name={selectedKey.toString()}
getInitials={name => name[0]!}
size='xs'
src={avatarUrl}
fallbackMode='bg'
className='px-2'
/>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button variant='onLight' role='combobox' aria-expanded={open} className='w-[300px]'>
{value ? (
<div className='flex gap-2'>
<Avatar className='size-6'>
<AvatarImage src={selected?.icon} />
<Identicon uniqueIdentifier={selected?.label ?? ''} type='gradient' size={22} />
</Avatar>
<span className='mt-0.5'>{selected?.label}</span>
</div>
) : (
<Box className='flex items-center justify-center px-2'>
<Skeleton width='24px' height='24px' className='rounded-full' />
</Box>
)
}
styleProps={{
width: {
mobile: '100%',
mdMobile: '350px',
},
}}
>
{filteredItems.map(info => (
<Combobox.Item
key={info.value}
textValue={info.label}
aria-label={`Select ${info.label}`}
>
<ChainOption chainInfo={info} />
</Combobox.Item>
))}
</Combobox>
</Box>
'Select a chain'
)}
<ChevronsUpDown className='ml-2 size-4 shrink-0 opacity-50' />
</Button>
</PopoverTrigger>
<PopoverContent className='w-[300px] p-0'>
<Command>
<CommandInput placeholder='Search chains...' />
<CommandEmpty>No framework found.</CommandEmpty>
<CommandGroup>
{chainInfos.map(chain => (
<CommandItem
key={chain.value}
value={chain.value}
onSelect={currentValue => {
setOpen(false);

if (currentValue === value) {
setValue('');
setSelectedChain(undefined);
} else {
setValue(currentValue);
const match = chainInfos.find(options => options.value === currentValue);
setSelectedChain(match);
}
}}
className='flex gap-2'
>
<Avatar className='size-6'>
<AvatarImage src={chain.icon} />
<Identicon uniqueIdentifier={chain.label} type='gradient' size={22} />
</Avatar>
{chain.label}
</CommandItem>
))}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
);
};
13 changes: 0 additions & 13 deletions apps/minifront/src/components/ibc/ibc-in/chain-picker.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export const IbcChainProvider = ({ registry, children }: IbcProviderProps) => {
},
}}
signerOptions={signerOptions}
modalTheme={{ defaultTheme: 'light' }}
>
{children}
</ChainProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useStore } from '../../../state';
import { ibcInSelector } from '../../../state/ibc-in';
import { useChain, useManager } from '@cosmos-kit/react';
import { WalletStatus } from '@cosmos-kit/core';
import { WalletAddrCard } from './wallet-addr-card';
import { ConnectWalletButton } from './wallet-connect-button';

export const useChainConnector = () => {
const { selectedChain } = useStore(ibcInSelector);
const { chainRecords } = useManager();
const defaultChain = chainRecords[0]!.name;
return useChain(selectedChain?.chainName ?? defaultChain);
};

export const CosmosWalletConnector = () => {
const { selectedChain } = useStore(ibcInSelector);
const { username, address, status, message } = useChainConnector();

return (
<div className='flex flex-col items-center justify-center gap-4'>
{address && selectedChain && <WalletAddrCard username={username} address={address} />}
<div className='w-52'>
<ConnectWalletButton />
</div>
{(status === WalletStatus.Rejected || status === WalletStatus.Error) && (
<div className='text-purple-500'>{message}</div>
)}
</div>
);
};
2 changes: 1 addition & 1 deletion apps/minifront/src/components/ibc/ibc-in/ibc-in-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const IbcInForm = () => {
}}
>
<InterchainUi />
<Button type='submit' variant='onLight'>
<Button type='submit' variant='onLight' disabled>
<div className='flex items-center gap-2'>
<LockClosedIcon />
<span className='-mb-1'>Shield Assets</span>
Expand Down
9 changes: 5 additions & 4 deletions apps/minifront/src/components/ibc/ibc-in/interchain-ui.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { IbcChainProvider } from './chain-provider';
import { useRegistry } from '../../../fetchers/registry';
import { ChainPicker } from './chain-picker';
import { ChainDropdown } from './chain-dropdown';
import { CosmosWalletConnector } from './cosmos-wallet-connector';

export const InterchainUi = () => {
const { data, isLoading, error } = useRegistry();
Expand All @@ -12,10 +13,10 @@ export const InterchainUi = () => {
return (
<IbcChainProvider registry={data}>
{/* negative margin offsets div inserted by provider */}
<div className='-mt-4'>
<ChainPicker />
<div className='-mt-4 flex justify-center'>
<ChainDropdown />
</div>
{/* WalletSection to go here */}
<CosmosWalletConnector />
</IbcChainProvider>
);
};
16 changes: 16 additions & 0 deletions apps/minifront/src/components/ibc/ibc-in/wallet-addr-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Identicon } from '@penumbra-zone/ui/components/ui/identicon';

interface UserInfoProps {
address: string;
username?: string;
}

export const WalletAddrCard = ({ address, username }: UserInfoProps) => {
return (
<div className='flex flex-col items-center gap-2 space-y-1 rounded-lg bg-white p-6'>
<Identicon uniqueIdentifier={address} type='gradient' size={42} />
<div className='flex items-center justify-center gap-4 text-gray-500'>{address}</div>
<span className='text-sm font-semibold text-stone-700 sm:text-xl'>{username}</span>
</div>
);
};
Loading

0 comments on commit d03bb8c

Please sign in to comment.