Add account filtering feature (#832)
* add account filter * Update packages/extension-ui/src/partials/Header.tsx
This commit is contained in:
parent
926848f056
commit
3d5d63b00a
@ -3,9 +3,12 @@
|
||||
|
||||
import type { ThemeProps } from '../../types';
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { AccountWithChildren } from '@polkadot/extension-base/background/types';
|
||||
import getNetworkMap from '@polkadot/extension-ui/util/getNetworkMap';
|
||||
|
||||
import { AccountContext } from '../../components';
|
||||
import useTranslation from '../../hooks/useTranslation';
|
||||
import { Header } from '../../partials';
|
||||
@ -18,7 +21,25 @@ interface Props extends ThemeProps {
|
||||
|
||||
function Accounts ({ className }: Props): React.ReactElement {
|
||||
const { t } = useTranslation();
|
||||
const [filter, setFilter] = useState('');
|
||||
const [filteredAccount, setFilteredAccount] = useState<AccountWithChildren[]>([]);
|
||||
const { hierarchy } = useContext(AccountContext);
|
||||
const networkMap = useMemo(() => getNetworkMap(), []);
|
||||
|
||||
useEffect(() => {
|
||||
setFilteredAccount(
|
||||
filter
|
||||
? hierarchy.filter((account) =>
|
||||
account.name?.toLowerCase().includes(filter) ||
|
||||
(account.genesisHash && networkMap.get(account.genesisHash)?.toLowerCase().includes(filter))
|
||||
)
|
||||
: hierarchy
|
||||
);
|
||||
}, [filter, hierarchy, networkMap]);
|
||||
|
||||
const _onFilter = useCallback((filter: string) => {
|
||||
setFilter(filter.toLowerCase());
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -27,12 +48,14 @@ function Accounts ({ className }: Props): React.ReactElement {
|
||||
: (
|
||||
<>
|
||||
<Header
|
||||
onFilter={_onFilter}
|
||||
showAdd
|
||||
showSearch
|
||||
showSettings
|
||||
text={t<string>('Accounts')}
|
||||
/>
|
||||
<div className={className}>
|
||||
{hierarchy.map((json, index): React.ReactNode => (
|
||||
{filteredAccount.map((json, index): React.ReactNode => (
|
||||
<AccountsTree
|
||||
{...json}
|
||||
key={`${index}:${json.address}`}
|
||||
|
||||
@ -51,6 +51,7 @@ function AuthManagement ({ className }: Props): React.ReactElement<Props> {
|
||||
onChange={_onChangeFilter}
|
||||
placeholder={t<string>('example.com')}
|
||||
value={filter}
|
||||
withReset
|
||||
/>
|
||||
<div className={className}>
|
||||
{
|
||||
|
||||
@ -1,23 +1,34 @@
|
||||
// Copyright 2017-2021 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { faTimes } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { ThemeProps } from '../types';
|
||||
import { Input } from './TextInputs';
|
||||
|
||||
interface Props {
|
||||
interface Props extends ThemeProps {
|
||||
className?: string;
|
||||
onChange: (filter: string) => void;
|
||||
placeholder: string;
|
||||
value: string;
|
||||
withReset?: boolean;
|
||||
}
|
||||
|
||||
function InputFilter ({ className, onChange, placeholder, value }: Props) {
|
||||
function InputFilter ({ className, onChange, placeholder, value, withReset = false }: Props) {
|
||||
const inputRef: React.RefObject<HTMLInputElement> | null = useRef(null);
|
||||
|
||||
const onChangeFilter = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
onChange(event.target.value);
|
||||
}, [onChange]);
|
||||
|
||||
const onResetFilter = useCallback(() => {
|
||||
onChange('');
|
||||
inputRef.current && inputRef.current.select();
|
||||
}, [onChange]);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Input
|
||||
@ -26,15 +37,32 @@ function InputFilter ({ className, onChange, placeholder, value }: Props) {
|
||||
autoFocus
|
||||
onChange={onChangeFilter}
|
||||
placeholder={placeholder}
|
||||
ref={inputRef}
|
||||
spellCheck={false}
|
||||
type='text'
|
||||
value={value}
|
||||
/>
|
||||
{withReset && !!value && (
|
||||
<FontAwesomeIcon
|
||||
className='resetIcon'
|
||||
icon={faTimes}
|
||||
onClick={onResetFilter}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default styled(InputFilter)`
|
||||
export default styled(InputFilter)(({ theme }: Props) => `
|
||||
padding-left: 1rem !important;
|
||||
padding-right: 1rem !important;
|
||||
`;
|
||||
position: relative;
|
||||
|
||||
.resetIcon {
|
||||
position: absolute;
|
||||
right: 28px;
|
||||
top: 12px;
|
||||
color: ${theme.iconNeutralColor};
|
||||
cursor: pointer;
|
||||
}
|
||||
`);
|
||||
|
||||
@ -3,30 +3,37 @@
|
||||
|
||||
import type { ThemeProps } from '../types';
|
||||
|
||||
import { faArrowLeft, faCog, faPlusCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faArrowLeft, faCog, faPlusCircle, faSearch } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import logo from '../assets/pjs.svg';
|
||||
import InputFilter from '../components/InputFilter';
|
||||
import Link from '../components/Link';
|
||||
import useOutsideClick from '../hooks/useOutsideClick';
|
||||
import useTranslation from '../hooks/useTranslation';
|
||||
import MenuAdd from './MenuAdd';
|
||||
import MenuSettings from './MenuSettings';
|
||||
|
||||
interface Props extends ThemeProps {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
onFilter?: (filter: string) => void;
|
||||
showAdd?: boolean;
|
||||
showBackArrow?: boolean;
|
||||
showSearch?: boolean;
|
||||
showSettings?: boolean;
|
||||
smallMargin?: boolean;
|
||||
text?: React.ReactNode;
|
||||
}
|
||||
|
||||
function Header ({ children, className = '', showAdd, showBackArrow, showSettings, smallMargin = false, text }: Props): React.ReactElement<Props> {
|
||||
function Header ({ children, className = '', onFilter, showAdd, showBackArrow, showSearch, showSettings, smallMargin = false, text }: Props): React.ReactElement<Props> {
|
||||
const [isAddOpen, setShowAdd] = useState(false);
|
||||
const [isSettingsOpen, setShowSettings] = useState(false);
|
||||
const [isSearchOpen, setShowSearch] = useState(false);
|
||||
const [filter, setFilter] = useState('');
|
||||
const { t } = useTranslation();
|
||||
const addRef = useRef(null);
|
||||
const setRef = useRef(null);
|
||||
|
||||
@ -48,6 +55,22 @@ function Header ({ children, className = '', showAdd, showBackArrow, showSetting
|
||||
[]
|
||||
);
|
||||
|
||||
const _onChangeFilter = useCallback((filter: string) => {
|
||||
setFilter(filter);
|
||||
onFilter && onFilter(filter);
|
||||
}, [onFilter]);
|
||||
|
||||
const _toggleSearch = useCallback(
|
||||
(): void => {
|
||||
if (isSearchOpen) {
|
||||
_onChangeFilter('');
|
||||
}
|
||||
|
||||
setShowSearch((isSearchOpen) => !isSearchOpen);
|
||||
},
|
||||
[_onChangeFilter, isSearchOpen]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={`${className} ${smallMargin ? 'smallMargin' : ''}`}>
|
||||
<div className='container'>
|
||||
@ -73,6 +96,25 @@ function Header ({ children, className = '', showAdd, showBackArrow, showSetting
|
||||
}
|
||||
<span className='logoText'>{text || 'polkadot{.js}'}</span>
|
||||
</div>
|
||||
{showSearch && (
|
||||
<div className={`searchBarWrapper ${isSearchOpen ? 'selected' : ''}`}>
|
||||
{isSearchOpen && (
|
||||
<InputFilter
|
||||
className='inputFilter'
|
||||
onChange={_onChangeFilter}
|
||||
placeholder={t<string>('Search by name or network...')}
|
||||
value={filter}
|
||||
withReset
|
||||
/>
|
||||
)}
|
||||
<FontAwesomeIcon
|
||||
className={`searchIcon ${isSearchOpen ? 'selected' : ''}`}
|
||||
icon={faSearch}
|
||||
onClick={_toggleSearch}
|
||||
size='lg'
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className='popupMenus'>
|
||||
{showAdd && (
|
||||
<div
|
||||
@ -154,29 +196,48 @@ export default React.memo(styled(Header)(({ theme }: Props) => `
|
||||
}
|
||||
}
|
||||
|
||||
.popupMenus {
|
||||
align-self: center;
|
||||
.popupMenus, .searchBarWrapper {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.popupToggle {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 24px;
|
||||
}
|
||||
.searchBarWrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
align-items: center;;
|
||||
|
||||
.searchIcon {
|
||||
margin-right: 8px;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.popupToggle+.popupToggle {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.popupToggle {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.inputFilter {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.popupToggle+.popupToggle {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.plusIcon, .cogIcon {
|
||||
.plusIcon, .cogIcon, .searchIcon {
|
||||
color: ${theme.iconNeutralColor};
|
||||
|
||||
&.selected {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user