1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-22 17:33:23 +01:00

[FLASK] Redesign dropdown-tab (#18546)

This commit is contained in:
Guillaume Roux 2023-04-18 12:46:38 +02:00 committed by GitHub
parent ae0af1b283
commit 76d79d9cce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 212 additions and 94 deletions

View File

@ -1,65 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import Dropdown from '../../dropdown';
import Box from '../../box';
export const DropdownTab = (props) => {
const {
activeClassName,
className,
'data-testid': dataTestId,
isActive,
onClick,
onChange,
tabIndex,
options,
selectedOption,
} = props;
return (
<Box
as="li"
className={classnames('tab', className, {
'tab--active': isActive,
[activeClassName]: activeClassName && isActive,
})}
data-testid={dataTestId}
onClick={(event) => {
event.preventDefault();
onClick(tabIndex);
}}
>
<Dropdown
options={options}
selectedOption={selectedOption}
onChange={onChange}
/>
</Box>
);
};
DropdownTab.propTypes = {
activeClassName: PropTypes.string,
className: PropTypes.string,
'data-testid': PropTypes.string,
isActive: PropTypes.bool, // required, but added using React.cloneElement
options: PropTypes.arrayOf(
PropTypes.exact({
name: PropTypes.string,
value: PropTypes.string.isRequired,
}),
).isRequired,
selectedOption: PropTypes.string,
onChange: PropTypes.func,
onClick: PropTypes.func,
tabIndex: PropTypes.number, // required, but added using React.cloneElement
};
DropdownTab.defaultProps = {
activeClassName: undefined,
className: undefined,
onChange: undefined,
onClick: undefined,
selectedOption: undefined,
};

View File

@ -1,23 +0,0 @@
.tab {
.dropdown__select {
border: none;
font-size: unset;
width: 100%;
background-color: unset;
padding-left: 8px;
padding-right: 20px;
line-height: unset;
option {
background-color: var(--color-background-default);
}
&:focus-visible {
outline: none;
}
}
.dropdown__icon-caret-down {
right: 0;
}
}

View File

@ -0,0 +1,160 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import Box from '../../../box';
import {
AlignItems,
BLOCK_SIZES,
BackgroundColor,
BorderColor,
BorderRadius,
BorderStyle,
DISPLAY,
FLEX_DIRECTION,
FLEX_WRAP,
TextVariant,
} from '../../../../../helpers/constants/design-system';
import { Icon, IconName, IconSize, Text } from '../../../../component-library';
export const DropdownTab = ({
activeClassName,
className,
'data-testid': dataTestId,
isActive,
onClick,
onChange,
tabIndex,
options,
selectedOption,
}) => {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef(null);
const selectOption = useCallback(
(event, option) => {
event.stopPropagation();
onChange(option.value);
setIsOpen(false);
},
[onChange],
);
const openDropdown = (event) => {
event.preventDefault();
setIsOpen(true);
onClick(tabIndex);
};
const selectedOptionName = options.find(
(option) => option.value === selectedOption,
)?.name;
useEffect(() => {
function handleClickOutside(event) {
if (
dropdownRef.current &&
!dropdownRef.current.contains(event.target) &&
isOpen
) {
setIsOpen(false);
}
}
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [dropdownRef, isOpen]);
return (
<Box
as="li"
className={classnames('tab', className, {
'tab--active': isActive,
[activeClassName]: activeClassName && isActive,
})}
data-testid={dataTestId}
onClick={openDropdown}
dataTestId={dataTestId}
flexDirection={FLEX_DIRECTION.ROW}
flexWrap={FLEX_WRAP.NO_WRAP}
height={BLOCK_SIZES.FULL}
style={{ cursor: 'pointer', overflow: 'hidden' }}
title={selectedOptionName}
>
<Box alignItems={AlignItems.center} padding={2}>
<Text
variant={TextVariant.inherit}
style={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{selectedOptionName}
</Text>
<Icon marginLeft={2} name={IconName.ArrowDown} size={IconSize.Sm} />
</Box>
{isOpen && (
<Box
backgroundColor={BackgroundColor.backgroundDefault}
borderStyle={BorderStyle.solid}
borderColor={BorderColor.borderDefault}
borderRadius={BorderRadius.SM}
paddingLeft={2}
paddingRight={2}
display={DISPLAY.FLEX}
flexDirection={FLEX_DIRECTION.COLUMN}
flexWrap={FLEX_WRAP.NO_WRAP}
style={{ position: 'absolute', maxWidth: '170px' }}
ref={dropdownRef}
>
{options.map((option, i) => (
<Text
key={i}
marginTop={1}
marginBottom={1}
variant={TextVariant.bodySm}
onClick={(event) => selectOption(event, option)}
style={{
cursor: 'pointer',
textTransform: 'none',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{option.name}
</Text>
))}
</Box>
)}
</Box>
);
};
DropdownTab.propTypes = {
activeClassName: PropTypes.string,
className: PropTypes.string,
'data-testid': PropTypes.string,
isActive: PropTypes.bool, // required, but added using React.cloneElement
options: PropTypes.arrayOf(
PropTypes.exact({
name: PropTypes.string,
value: PropTypes.string.isRequired,
}),
).isRequired,
selectedOption: PropTypes.string,
onChange: PropTypes.func,
onClick: PropTypes.func,
tabIndex: PropTypes.number, // required, but added using React.cloneElement
};
DropdownTab.defaultProps = {
activeClassName: undefined,
className: undefined,
onChange: undefined,
onClick: undefined,
selectedOption: undefined,
};

View File

@ -0,0 +1,48 @@
import * as React from 'react';
import { render, fireEvent } from '@testing-library/react';
import DropdownTab from '.';
describe('DropdownTab', () => {
const onChange = jest.fn();
const onClick = jest.fn();
let args;
beforeEach(() => {
args = {
activeClassName: 'active',
tabIndex: 1,
options: [
{ name: 'foo', value: 'foo' },
{ name: 'bar', value: 'bar' },
],
selectedOption: 'foo',
onChange,
onClick,
};
});
it('should render the DropdownTab component without crashing', () => {
const { getByText } = render(<DropdownTab {...args} />);
expect(getByText(args.options[0].name)).toBeDefined();
});
it('registers click', () => {
const { container } = render(<DropdownTab {...args} />);
fireEvent.click(container.firstChild);
expect(onClick).toHaveBeenCalledWith(args.tabIndex);
});
it('registers selection', () => {
const { container, getByText } = render(<DropdownTab {...args} />);
fireEvent.click(container.firstChild);
const element = getByText(args.options[1].name);
fireEvent.click(element);
expect(onClick).toHaveBeenCalledWith(args.tabIndex);
expect(onChange).toHaveBeenCalledWith(args.options[1].value);
});
});

View File

@ -1,5 +1,4 @@
import Tabs from './tabs.component';
import Tab from './tab';
import DropdownTab from './dropdown-tab';
export { Tabs, Tab, DropdownTab };
export { Tabs, Tab };

View File

@ -1,5 +1,4 @@
@import 'tab/index';
@import 'dropdown-tab/index';
.tabs {
flex-grow: 1;
@ -11,6 +10,5 @@
position: sticky;
top: 0;
z-index: 2;
overflow: hidden;
}
}

View File

@ -1,5 +1,5 @@
import React from 'react';
import DropdownTab from './dropdown-tab';
import DropdownTab from './flask/dropdown-tab';
import Tab from './tab/tab.component';
import Tabs from './tabs.component';

View File

@ -5,7 +5,8 @@ import { CHAIN_ID_TO_NETWORK_ID_MAP } from '../../shared/constants/network';
import { stripHexPrefix } from '../../shared/modules/hexstring-utils';
import { TransactionType } from '../../shared/constants/transaction';
import { getInsightSnaps } from '../selectors';
import { DropdownTab, Tab } from '../components/ui/tabs';
import { Tab } from '../components/ui/tabs';
import DropdownTab from '../components/ui/tabs/flask/dropdown-tab';
import { SnapInsight } from '../components/app/confirm-page-container/flask/snap-insight';
const isAllowedTransactionTypes = (transactionType) =>