Skip to content

Commit c443eff

Browse files
committed
frontend: styled multi select for Active Currencies Dropdown / Select
to improve our UI, we'd like to style our Active Currencies multi dropdown. In this commit we also refactored it, along with the single dropdown component.
1 parent 17b231b commit c443eff

File tree

11 files changed

+220
-98
lines changed

11 files changed

+220
-98
lines changed

frontends/web/src/api/account.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ export type CoinUnit = 'BTC' | 'sat' | 'LTC' | 'ETH' | 'TBTC' | 'tsat' | 'TLTC'
2929

3030
export type ERC20TokenUnit = 'USDT' | 'USDC' | 'LINK' | 'BAT' | 'MKR' | 'ZRX' | 'WBTC' | 'PAXG' | 'SAI' | 'DAI';
3131

32+
export type FiatWithDisplayName = {
33+
currency: Fiat,
34+
displayName: string
35+
}
36+
3237
export type Terc20Token = {
3338
code: string;
3439
name: string;
Lines changed: 3 additions & 0 deletions
Loading

frontends/web/src/components/icon/icon.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import saveSVG from './assets/icons/save.svg';
5252
import saveLightSVG from './assets/icons/save-light.svg';
5353
import starSVG from './assets/icons/star.svg';
5454
import starInactiveSVG from './assets/icons/star-inactive.svg';
55+
import selectedCheckLight from './assets/icons/selected-check-light.svg';
5556
import style from './icon.module.css';
5657

5758
export const ExpandOpen = () => (
@@ -156,6 +157,8 @@ export const Save = (props: ImgProps) => (<img src={saveSVG} draggable={false} {
156157
export const SaveLight = (props: ImgProps) => (<img src={saveLightSVG} draggable={false} {...props} />);
157158
export const Star = (props: ImgProps) => (<img src={starSVG} draggable={false} {...props} />);
158159
export const StarInactive = (props: ImgProps) => (<img src={starInactiveSVG} draggable={false} {...props} />);
160+
// check component for cusotm multi-dropdown select
161+
export const SelectedCheckLight = (props: ImgProps) => (<img src={selectedCheckLight} draggable={false} {...props} />);
159162
/**
160163
* @deprecated Alert is only used for BitBox01 use `Warning` icon instead
161164
*/

frontends/web/src/components/rates/rates.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { Fiat, ConversionUnit, IAmount } from '../../api/account';
18+
import { Fiat, ConversionUnit, IAmount, FiatWithDisplayName } from '../../api/account';
1919
import { BtcUnit } from '../../api/coins';
2020
import { reinitializeAccounts } from '../../api/backend';
2121
import { share } from '../../decorators/share';
@@ -34,6 +34,25 @@ export interface SharedProps {
3434
}
3535

3636
export const currencies: Fiat[] = ['AUD', 'BRL', 'CAD', 'CHF', 'CNY', 'EUR', 'GBP', 'HKD', 'ILS', 'JPY', 'KRW', 'NOK', 'RUB', 'SEK', 'SGD', 'USD', 'BTC'];
37+
export const currenciesWithDisplayName: FiatWithDisplayName[] = [
38+
{ currency: 'AUD', displayName: 'Australian dollar' },
39+
{ currency: 'BRL', displayName: 'Brazilian real' },
40+
{ currency: 'CAD', displayName: 'Canadian dollar' },
41+
{ currency: 'CHF', displayName: 'Swiss franc' },
42+
{ currency: 'CNY', displayName: 'Chinese yuan' },
43+
{ currency: 'EUR', displayName: 'Euro' },
44+
{ currency: 'GBP', displayName: 'British pound' },
45+
{ currency: 'HKD', displayName: 'Hong Kong dollar' },
46+
{ currency: 'ILS', displayName: 'Israeli new shekel' },
47+
{ currency: 'JPY', displayName: 'Japanese yen' },
48+
{ currency: 'KRW', displayName: 'South Korean won' },
49+
{ currency: 'NOK', displayName: 'Norwegian krone' },
50+
{ currency: 'RUB', displayName: 'Russian ruble' },
51+
{ currency: 'SEK', displayName: 'Swedish krona' },
52+
{ currency: 'SGD', displayName: 'Singapore dollar' },
53+
{ currency: 'USD', displayName: 'United States dollar' },
54+
{ currency: 'BTC', displayName: 'Bitcoin' }
55+
];
3756

3857
export const store = new Store<SharedProps>({
3958
active: 'USD',

frontends/web/src/routes/new-settings/components/appearance/activeCurrenciesDropdownSetting.tsx

Lines changed: 4 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -14,77 +14,20 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { useEffect, useState } from 'react';
18-
import Select, { ActionMeta, MultiValue, MultiValueRemoveProps, components } from 'react-select';
19-
import { currencies, store, selectFiat, unselectFiat, SharedProps } from '../../../../components/rates/rates';
17+
import { store, SharedProps, currenciesWithDisplayName } from '../../../../components/rates/rates';
2018
import { SettingsItem } from '../settingsItem/settingsItem';
21-
import { Fiat } from '../../../../api/account';
2219
import { share } from '../../../../decorators/share';
23-
24-
type SelectOption = {
25-
label: Fiat;
26-
value: Fiat;
27-
}
28-
29-
type TSelectProps = {
30-
options: SelectOption[];
31-
} & SharedProps;
32-
33-
const ReactSelect = ({ options, active, selected }: TSelectProps) => {
34-
const [selectedCurrencies, setSelectedCurrencies] = useState<SelectOption[]>([]);
35-
36-
useEffect(() => {
37-
if (selected.length > 0) {
38-
const formattedSelectedCurrencies = selected.map(currency => ({ label: currency, value: currency }));
39-
setSelectedCurrencies(formattedSelectedCurrencies);
40-
}
41-
}, [selected]);
42-
43-
const MultiValueRemove = (props: MultiValueRemoveProps<SelectOption>) => {
44-
const currency = props.data.value;
45-
return (
46-
currency !== active ?
47-
<components.MultiValueRemove {...props}>
48-
{'X'}
49-
</components.MultiValueRemove>
50-
: null
51-
);
52-
};
53-
54-
return (
55-
<Select
56-
classNamePrefix="react-select"
57-
isSearchable
58-
isClearable={false}
59-
components={{ MultiValueRemove }}
60-
isMulti
61-
value={selectedCurrencies}
62-
onChange={(selectedFiats: MultiValue<SelectOption>, meta: ActionMeta<SelectOption>) => {
63-
switch (meta.action) {
64-
case 'remove-value':
65-
if (selectedFiats.length > 0) {
66-
const unselectedFiat = meta.removedValue.value;
67-
unselectFiat(unselectedFiat);
68-
}
69-
break;
70-
case 'select-option':
71-
const selectedFiat = selectedFiats[selectedFiats.length - 1].value as Fiat;
72-
selectFiat(selectedFiat);
73-
}
74-
}}
75-
options={options}
76-
/>);
77-
};
20+
import { ActiveCurrenciesDropdown } from '../dropdowns/activecurrenciesdropdown';
7821

7922
const ActiveCurrenciesDropdownSetting = ({ selected, active }: SharedProps) => {
80-
const formattedCurrencies = currencies.map((currency) => ({ label: currency, value: currency }));
23+
const formattedCurrencies = currenciesWithDisplayName.map((fiat) => ({ label: `${fiat.displayName} (${fiat.currency})`, value: fiat.currency }));
8124
return (
8225
<SettingsItem
8326
collapseOnSmall
8427
settingName="Active Currencies"
8528
secondaryText="These additional currencies can be toggled through on your account page."
8629
extraComponent={
87-
<ReactSelect
30+
<ActiveCurrenciesDropdown
8831
options={formattedCurrencies}
8932
active={active}
9033
selected={selected}

frontends/web/src/routes/new-settings/components/appearance/defaultCurrencyDropdownSetting.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { currencies, selectFiat, setActiveFiat, store } from '../../../../components/rates/rates';
18-
import { SingleDropdown } from '../singledropdown/singledropdown';
17+
import { currenciesWithDisplayName, selectFiat, setActiveFiat, store } from '../../../../components/rates/rates';
18+
import { SingleDropdown } from '../dropdowns/singledropdown';
1919
import { SettingsItem } from '../settingsItem/settingsItem';
2020
import { Fiat } from '../../../../api/account';
2121

2222
export const DefaultCurrencyDropdownSetting = () => {
23-
const formattedCurrencies = currencies.map((currency) => ({ label: currency, value: currency }));
24-
23+
const formattedCurrencies = currenciesWithDisplayName.map((fiat) => ({ label: `${fiat.displayName} (${fiat.currency})`, value: fiat.currency }));
24+
const defaultValueLabel = currenciesWithDisplayName.find(fiat => fiat.currency === store.state.active)?.displayName;
2525
return (
2626
<SettingsItem
2727
settingName="Default Currency"
@@ -36,7 +36,10 @@ export const DefaultCurrencyDropdownSetting = () => {
3636
selectFiat(fiat);
3737
}
3838
}}
39-
defaultValue={{ label: store.state.active, value: store.state.active }}
39+
defaultValue={{
40+
label: defaultValueLabel ? `${defaultValueLabel} (${store.state.active})` : store.state.active,
41+
value: store.state.active
42+
}}
4043
/>
4144
}
4245
/>

frontends/web/src/routes/new-settings/components/appearance/languageDropdownSetting.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { SettingsItem } from '../settingsItem/settingsItem';
1818
import { useTranslation } from 'react-i18next';
1919
import { TLanguagesList } from '../../../../components/language/types';
2020
import { getSelectedIndex } from '../../../../utils/language';
21-
import { SingleDropdown } from '../singledropdown/singledropdown';
21+
import { SingleDropdown } from '../dropdowns/singledropdown';
2222

2323
const defaultLanguages: TLanguagesList = [
2424
{ code: 'ar', display: 'العربية' },
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { useEffect, useState } from 'react';
2+
import Select, { ActionMeta, DropdownIndicatorProps, OptionProps, components } from 'react-select';
3+
import { selectFiat, unselectFiat, SharedProps } from '../../../../components/rates/rates';
4+
import { Fiat } from '../../../../api/account';
5+
import { SelectedCheckLight } from '../../../../components/icon';
6+
import dropdownStyles from './dropdowns.module.css';
7+
import multiSelectDropdownStyles from './multiselectdropdown.module.css';
8+
9+
type SelectOption = {
10+
label: String;
11+
value: Fiat;
12+
}
13+
14+
type TSelectProps = {
15+
options: SelectOption[];
16+
} & SharedProps;
17+
18+
19+
// a multi-select dropdown
20+
export const ActiveCurrenciesDropdown = ({ options, active, selected }: TSelectProps) => {
21+
const [selectedCurrencies, setSelectedCurrencies] = useState<SelectOption[]>([]);
22+
const [search, setSearch] = useState('');
23+
24+
useEffect(() => {
25+
if (selected.length > 0) {
26+
const formattedSelectedCurrencies = selected.map(currency => ({ label: currency, value: currency }));
27+
setSelectedCurrencies(formattedSelectedCurrencies);
28+
}
29+
}, [selected]);
30+
31+
const DropdownIndicator = (props: DropdownIndicatorProps<SelectOption, true>) => {
32+
return (
33+
<components.DropdownIndicator {...props}>
34+
<div className={dropdownStyles.dropdown} />
35+
</components.DropdownIndicator>
36+
);
37+
};
38+
39+
const Option = (props: OptionProps<SelectOption, true>) => {
40+
const { label, value } = props.data;
41+
const selected = selectedCurrencies.findIndex(currency => currency.value === value) >= 0;
42+
return (
43+
<components.Option {...props}>
44+
<span>{label}</span>
45+
{selected ? <SelectedCheckLight /> : null}
46+
</components.Option>
47+
);
48+
};
49+
return (
50+
<Select
51+
className={`
52+
${dropdownStyles.select}
53+
${multiSelectDropdownStyles.select}
54+
${search.length > 0 ? multiSelectDropdownStyles.hideMultiSelect : ''}
55+
`}
56+
classNamePrefix="react-select"
57+
isSearchable
58+
isClearable={false}
59+
components={{ DropdownIndicator, IndicatorSeparator: () => null, MultiValueRemove: () => null, Option }}
60+
isMulti
61+
closeMenuOnSelect={false}
62+
hideSelectedOptions={false}
63+
value={[...selectedCurrencies].reverse()}
64+
onInputChange={(newValue) => setSearch(newValue)}
65+
onChange={(_, meta: ActionMeta<SelectOption>) => {
66+
switch (meta.action) {
67+
case 'select-option':
68+
if (meta.option) {
69+
selectFiat(meta.option.value);
70+
}
71+
break;
72+
case 'deselect-option':
73+
if (meta.option && meta.option.value !== active) {
74+
unselectFiat(meta.option.value);
75+
}
76+
}
77+
78+
}}
79+
options={options}
80+
/>);
81+
};
Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,25 @@
77
padding: 0 calc(var(--space-quarter) + var(--space-eight));
88
}
99

10+
.select :global(.react-select__control) {
11+
background-color: var(--background-secondary);
12+
border: 2px solid var(--color-custom-select-border);
13+
border-radius: 0;
14+
padding: 0 var(--space-eight);
15+
}
16+
1017
.select {
1118
width: 280px;
19+
background-color: var(--background-secondary);
20+
color: var(--color-default);
21+
}
22+
23+
.select :global(.react-select__option.react-select__option--is-selected),
24+
.select :global(.react-select__option.react-select__option--is-selected):hover,
25+
.select :global(.react-select__option.react-select__option--is-focused),
26+
.select :global(.react-select__option.react-select__option--is-focused):hover {
27+
background-color: var(--background-custom-select-selected);
28+
color: var(--color-alt);
1229
}
1330

1431
.select :global(.react-select__menu) {
@@ -17,36 +34,42 @@
1734

1835
.select :global(.react-select__option) {
1936
background-color: var(--background-secondary);
37+
font-size: var(--size-default);
38+
color: var(--color-default);
39+
padding: var(--space-half) var(--space-quarter);
40+
cursor: pointer;
2041
}
2142

43+
2244
.select :global(.react-select__option):hover {
2345
background-color: var(--background-custom-select-hover);
2446
}
2547

26-
.select :global(.react-select__option--is-selected),
27-
.select :global(.react-select__option--is-selected):hover {
28-
background-color: var(--background-custom-select-selected);
29-
}
30-
31-
.select :global(.react-select__control) {
32-
background-color: var(--background-secondary);
33-
border: 2px solid var(--color-custom-select-border);
34-
border-radius: 0;
35-
padding: 0 var(--space-eight);
48+
.select :global(.react-select__menu-list) {
49+
padding: 0;
3650
}
3751

3852
.select :global(.react-select__control):hover {
3953
cursor: pointer;
4054
}
4155

42-
.valueContainer {
56+
.select :global(.react-select__single-value),
57+
.select :global(.react-select__input-container) {
4358
font-size: var(--size-default);
4459
color: var(--color-default);
45-
padding: 8px 0;
4660
}
4761

48-
.select :global(.react-select__option--is-selected) .optionValue {
49-
color: var(--color-alt);
62+
63+
.select :global(.react-select__multi-value) {
64+
padding: 0;
65+
margin: 0;
66+
background: transparent;
67+
}
68+
69+
.select :global(.react-select__multi-value__label) {
70+
padding: 0;
71+
color: var(--color-default);
72+
font-size: var(--size-default);
5073
}
5174

5275
@media (max-width: 560px) {

0 commit comments

Comments
 (0)