Skip to content

Commit a447c93

Browse files
committed
frontend: fullscreen selector on mobile
to improve the mobile UX, we've introduced a new component called MobileFullscreenSelector. This component is rendered automatically when the Dropdown component is used with the prop mobileFullScreen={true}. This feature is integrated in this commit across the following components: - ActiveCurrenciesDropdownSetting - DefaultCurrencyDropdownSetting - LanguageDropdownSetting
1 parent c0ba215 commit a447c93

12 files changed

+413
-14
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- Reduced support for BitBox01
66
- Fix a bug that would prevent the app to perform firmware upgrade when offline.
77
- Replace sidebar with bottom navigation bar for mobile devices
8+
- Introduce full screen selector for mobile in place of dropdown
89

910
# v4.47.2
1011
- Linux: fix compatiblity with some versions of Mesa that are incompatible with the bundled wayland libraries

frontends/web/src/components/dropdown/dropdown.module.css

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
.dropdown {
32
appearance: none;
43
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' aria-labelledby='chevron down' color='%23777'%3E%3Cdefs/%3E%3Cpolyline points='6 10 12 16 18 10'/%3E%3C/svg%3E%0A");

frontends/web/src/components/dropdown/dropdown.tsx

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
21
/**
3-
* Copyright 2024 Shift Crypto AG
2+
* Copyright 2024-2025 Shift Crypto AG
43
*
54
* Licensed under the Apache License, Version 2.0 (the "License");
65
* you may not use this file except in compliance with the License.
@@ -25,6 +24,8 @@ import Select, {
2524
Props as ReactSelectProps,
2625
ActionMeta,
2726
} from 'react-select';
27+
import { useMediaQuery } from '@/hooks/mediaquery';
28+
import { MobileFullscreenSelector } from './mobile-fullscreen-selector';
2829
import styles from './dropdown.module.css';
2930

3031
export type TOption<T = any> = {
@@ -41,6 +42,10 @@ type SelectProps<T = any, IsMulti extends boolean = false> = Omit<
4142
newValue: IsMulti extends true ? TOption<T>[] : TOption<T>,
4243
actionMeta: ActionMeta<TOption<T>>
4344
) => void;
45+
mobileFullScreen?: boolean;
46+
isOpen?: boolean;
47+
onOpenChange?: (isOpen: boolean) => void;
48+
title?: string;
4449
};
4550

4651
const DropdownIndicator = (props: DropdownIndicatorProps<TOption>) => (
@@ -89,7 +94,7 @@ const CustomMultiValue = ({ index, getValue }: MultiValueProps<TOption>) => {
8994
const hiddenCount = selectedValues.length - maxVisible;
9095

9196
return (
92-
<div className={styles.valueContainer}>
97+
<div>
9398
{hiddenCount > 0 ? `${displayedValues}...` : displayedValues}
9499
</div>
95100
);
@@ -100,8 +105,39 @@ export const Dropdown = <T, IsMulti extends boolean = false>({
100105
renderOptions,
101106
className,
102107
onChange,
108+
title = '',
109+
mobileFullScreen = false,
110+
isOpen,
111+
onOpenChange,
103112
...props
104113
}: SelectProps<T, IsMulti>) => {
114+
const isMobile = useMediaQuery('(max-width: 768px)');
115+
116+
if (isMobile && mobileFullScreen) {
117+
const options: TOption<T>[] = props.options
118+
? (props.options as TOption<T>[]).filter(
119+
(option): option is TOption<T> =>
120+
option !== null &&
121+
typeof option === 'object' &&
122+
'value' in option &&
123+
'label' in option
124+
)
125+
: [];
126+
127+
return (
128+
<MobileFullscreenSelector
129+
title={title}
130+
options={options}
131+
renderOptions={renderOptions}
132+
value={props.value as any}
133+
onSelect={onChange}
134+
isMulti={props.isMulti}
135+
isOpen={isOpen}
136+
onOpenChange={onOpenChange}
137+
/>
138+
);
139+
}
140+
105141
return (
106142
<Select
107143
className={`
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
.dropdown {
2+
appearance: none;
3+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' aria-labelledby='chevron down' color='%23777'%3E%3Cdefs/%3E%3Cpolyline points='6 10 12 16 18 10'/%3E%3C/svg%3E%0A");
4+
background-repeat: no-repeat;
5+
font-weight: 400;
6+
height: calc(var(--space-quarter) * 3);
7+
padding: 0 calc(var(--space-quarter) + var(--space-eight));
8+
transform: rotate(270deg);
9+
}
10+
11+
.dropdownContainer {
12+
width: 100%;
13+
}
14+
15+
.mobileDropdownTrigger {
16+
background-color: var(--background-secondary);
17+
border: none;
18+
border-radius: 2px;
19+
color: var(--color-default);
20+
cursor: pointer;
21+
display: flex;
22+
align-items: center;
23+
justify-content: space-between;
24+
font-size: var(--size-default);
25+
height: auto;
26+
padding: 0;
27+
width: 100%;
28+
text-align: end;
29+
}
30+
31+
.mobileDropdownValue {
32+
flex: 1;
33+
overflow: hidden;
34+
text-overflow: ellipsis;
35+
white-space: nowrap;
36+
color: var(--color-secondary);
37+
}
38+
39+
.fullscreenOverlay {
40+
background-color: var(--background-secondary);
41+
bottom: 0;
42+
left: 0;
43+
position: fixed;
44+
right: 0;
45+
top: 0;
46+
z-index: 9999;
47+
}
48+
49+
.fullscreenContent {
50+
display: flex;
51+
flex-direction: column;
52+
height: 100%;
53+
width: 100%;
54+
}
55+
56+
.fullscreenHeader {
57+
align-items: center;
58+
border-bottom: 1px solid var(--background);
59+
background-color: var(--background);
60+
cursor: default;
61+
display: flex;
62+
height: var(--item-height);
63+
padding: 0 var(--space-half);
64+
position: relative;
65+
}
66+
67+
.backButton {
68+
background: none;
69+
border: none;
70+
color: var(--color-default);
71+
cursor: pointer;
72+
padding-left: 0;
73+
margin-right: var(--space-quarter);
74+
}
75+
76+
.backButtonIcon {
77+
width: 24px;
78+
height: 24px;
79+
}
80+
81+
.fullscreenTitle {
82+
color: var(--color-default);
83+
flex: 1;
84+
font-size: var(--size-subheader);
85+
font-weight: 400;
86+
margin: 0;
87+
position: absolute;
88+
text-align: center;
89+
left: 50%;
90+
transform: translateX(-50%);
91+
}
92+
93+
.searchContainer {
94+
background-color: var(--background);
95+
border-bottom: 1px solid var(--background-secondary);
96+
padding: var(--space-half);
97+
padding-top: 0;
98+
}
99+
100+
.searchInput {
101+
background-color: var(--background-secondary);
102+
border: 1px solid var(--background-secondary);
103+
border-radius: 4px;
104+
color: var(--color-default);
105+
font-size: var(--size-default);
106+
width: 100%;
107+
padding: var(--space-quarter) var(--space-half);
108+
}
109+
.searchInput:focus {
110+
outline: none;
111+
}
112+
.optionsList {
113+
flex: 1;
114+
overflow-y: auto;
115+
padding: 0;
116+
}
117+
118+
.optionItem {
119+
align-items: center;
120+
background: none;
121+
border: none;
122+
color: var(--color-default);
123+
cursor: pointer;
124+
display: flex;
125+
font-size: var(--size-default);
126+
justify-content: space-between;
127+
padding: var(--space-half);
128+
text-align: left;
129+
width: 100%;
130+
}
131+
132+
.selectedOption {
133+
background-color: var(--background-custom-select-selected);
134+
}
135+
136+
.optionContent {
137+
flex: 1;
138+
}
139+
140+
.noOptions {
141+
padding: var(--space-half);
142+
text-align: center;
143+
color: var(--color-secondary);
144+
font-size: var(--size-default);
145+
}
146+
147+
@media screen and (max-width: 560px) {
148+
.mobileDropdownTrigger {
149+
padding-top: var(--space-quarter);
150+
text-align: start;
151+
}
152+
}

0 commit comments

Comments
 (0)