Skip to content

Commit 39c8dd4

Browse files
committed
Merge branch 'frontend-fullscreen-selector-for-mobile'
2 parents c0ba215 + dcd9c1c commit 39c8dd4

27 files changed

+446
-64
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/actionable-item/actionable-item.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,10 @@ export const ActionableItem = ({
4949
onClick={onClick}>
5050
{children}
5151
{icon ? icon : (
52-
<ChevronRightDark />
52+
<ChevronRightDark
53+
width={24}
54+
height={24}
55+
/>
5356
)}
5457
</button>
5558
)}

frontends/web/src/components/bottom-navigation/bottom-navigation.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export const BottomNavigation = () => {
4747
{t('generic.buySell')}
4848
</Link>
4949
<Link
50-
className={`${styles.link} ${pathname.startsWith('/settings/') || pathname.startsWith('/bitsurance/') ? styles.active : ''}`}
50+
className={`${styles.link} ${pathname.startsWith('/settings') || pathname.startsWith('/bitsurance/') ? styles.active : ''}`}
5151
to="/settings/more"
5252
>
5353
<MoreIconSVG />

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: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
.dropdownContainer {
2+
width: 100%;
3+
}
4+
5+
.mobileSelectorTrigger {
6+
background-color: var(--background-secondary);
7+
border: none;
8+
border-radius: 2px;
9+
color: var(--color-default);
10+
cursor: pointer;
11+
display: flex;
12+
align-items: center;
13+
justify-content: space-between;
14+
font-size: var(--size-default);
15+
height: auto;
16+
padding: 0;
17+
width: 100%;
18+
text-align: end;
19+
}
20+
21+
.mobileSelectorValue {
22+
flex: 1;
23+
overflow: hidden;
24+
text-overflow: ellipsis;
25+
white-space: nowrap;
26+
color: var(--color-secondary);
27+
}
28+
29+
.fullscreenOverlay {
30+
background-color: var(--background-secondary);
31+
bottom: 0;
32+
left: 0;
33+
position: fixed;
34+
right: 0;
35+
top: 0;
36+
z-index: 9999;
37+
}
38+
39+
.fullscreenContent {
40+
display: flex;
41+
flex-direction: column;
42+
height: 100%;
43+
width: 100%;
44+
}
45+
46+
.fullscreenHeader {
47+
align-items: center;
48+
border-bottom: 1px solid var(--background);
49+
background-color: var(--background);
50+
cursor: default;
51+
display: flex;
52+
height: var(--item-height);
53+
padding: 0 var(--space-half);
54+
position: relative;
55+
}
56+
57+
.backButton {
58+
background: none;
59+
border: none;
60+
color: var(--color-default);
61+
cursor: pointer;
62+
padding-left: 0;
63+
margin-right: var(--space-quarter);
64+
}
65+
66+
.backButtonIcon {
67+
width: 24px;
68+
height: 24px;
69+
}
70+
71+
.fullscreenTitle {
72+
color: var(--color-default);
73+
flex: 1;
74+
font-size: var(--size-subheader);
75+
font-weight: 400;
76+
margin: 0;
77+
position: absolute;
78+
text-align: center;
79+
left: 50%;
80+
transform: translateX(-50%);
81+
}
82+
83+
.searchContainer {
84+
background-color: var(--background);
85+
border-bottom: 1px solid var(--background-secondary);
86+
padding: var(--space-half);
87+
padding-top: 0;
88+
}
89+
90+
.searchInput {
91+
background-color: var(--background-secondary);
92+
border: 1px solid var(--background-secondary);
93+
border-radius: 4px;
94+
color: var(--color-default);
95+
font-size: var(--size-default);
96+
width: 100%;
97+
padding: var(--space-quarter) var(--space-half);
98+
}
99+
.searchInput:focus {
100+
outline: none;
101+
}
102+
.optionsList {
103+
flex: 1;
104+
overflow-y: auto;
105+
padding: 0;
106+
}
107+
108+
.optionItem {
109+
align-items: center;
110+
background: none;
111+
border: none;
112+
color: var(--color-default);
113+
cursor: pointer;
114+
display: flex;
115+
font-size: var(--size-default);
116+
justify-content: space-between;
117+
padding: var(--space-half);
118+
text-align: left;
119+
width: 100%;
120+
}
121+
122+
.selectedOption {
123+
background-color: var(--background-custom-select-selected);
124+
}
125+
126+
.optionContent {
127+
flex: 1;
128+
}
129+
130+
.noOptions {
131+
padding: var(--space-half);
132+
text-align: center;
133+
color: var(--color-secondary);
134+
font-size: var(--size-default);
135+
}
136+
137+
@media screen and (max-width: 560px) {
138+
.mobileSelectorTrigger {
139+
padding-top: var(--space-quarter);
140+
text-align: start;
141+
}
142+
}

0 commit comments

Comments
 (0)