Skip to content

Commit cac670c

Browse files
committed
feat(sports): replace multiselect filters with HDS selects
HCRC-178
1 parent a291d5b commit cac670c

12 files changed

Lines changed: 156 additions & 368 deletions

File tree

apps/sports-helsinki/src/domain/search/combinedSearch/SearchForm.tsx

Lines changed: 55 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
import {
22
useSearchTranslation,
3-
MultiSelectDropdown,
43
useAppSportsTranslation,
54
IconPersonRunning,
6-
SearchSelect,
75
AdvancedSearchTextInput,
86
} from '@events-helsinki/components';
97
import classNames from 'classnames';
10-
import type { SelectCustomTheme, Option } from 'hds-react';
8+
import type { Option } from 'hds-react';
119
import {
1210
Button,
1311
ButtonVariant,
1412
IconGroup,
1513
IconPersonWheelchair,
1614
IconSearch,
15+
Select,
1716
} from 'hds-react';
1817
import React from 'react';
1918
import AppConfig from '../../app/AppConfig';
@@ -43,13 +42,9 @@ export const SimpleSearchForm: React.FC<SearchComponentType> = ({
4342
setTextSearchInput,
4443
selectedSportsCategories,
4544
setSelectedSportsCategories,
46-
sportsCategoryInput,
47-
setSportsCategoryInput,
4845
sportsCategories,
4946
selectedTargetGroups,
5047
setSelectedTargetGroups,
51-
targetGroupInput,
52-
setTargetGroupInput,
5348
targetGroups,
5449
accessibilityProfiles,
5550
selectedAccessibilityProfile,
@@ -100,7 +95,7 @@ export const SimpleSearchForm: React.FC<SearchComponentType> = ({
10095
<p className={styles.searchDescription}>{description}</p>
10196
)}
10297
<div className={styles.rowWrapper}>
103-
<div>
98+
<div className={classNames(styles.textSearchRow)}>
10499
<AdvancedSearchTextInput
105100
id="search"
106101
name="search"
@@ -115,53 +110,83 @@ export const SimpleSearchForm: React.FC<SearchComponentType> = ({
115110
</div>
116111
<div className={styles.rowWrapper}>
117112
<div
118-
className={
113+
className={classNames(
119114
AppConfig.showTargetGroupFilter
120115
? styles.rowWithTargetGroupFilter
121116
: styles.rowWithoutTargetGroupFilter
122-
}
117+
)}
123118
>
124119
<div>
125-
<MultiSelectDropdown
126-
checkboxName="sportsCategoryOptions"
120+
<Select
121+
className={styles.categoriesSelector}
122+
id="sportsCategory"
127123
icon={<IconPersonRunning aria-hidden />}
128-
inputValue={sportsCategoryInput}
129-
name="sportsCategory"
130-
onChange={setSelectedSportsCategories}
124+
onChange={(selectedOptions: Option[]) =>
125+
setSelectedSportsCategories(
126+
selectedOptions.map((option) => option.value)
127+
)
128+
}
131129
options={sportsCategories}
132-
setInputValue={setSportsCategoryInput}
133-
showSearch={false}
134-
title={t('search.titleDropdownSportsCategory')}
130+
visibleOptions={5.97} // use decimal to make scrollable content visible
131+
texts={{
132+
placeholder: t('search.titleDropdownSportsCategory'),
133+
}}
135134
value={selectedSportsCategories}
135+
theme={{
136+
'--checkbox-background-selected': 'var(--color-input-dark)',
137+
'--checkbox-background-hover': 'var(--color-input-dark)',
138+
'--menu-item-background-color-hover':
139+
'var(--color-input-light)',
140+
'--menu-item-background-color-selected-hover':
141+
'var(--color-input-light)',
142+
}}
143+
multiSelect
144+
noTags
136145
/>
137146
</div>
138147
{AppConfig.showTargetGroupFilter && (
139148
<div>
140-
<MultiSelectDropdown
141-
checkboxName="targetGroupOptions"
149+
<Select
150+
className={styles.targetGroupSelector}
142151
icon={<IconGroup aria-hidden />}
143-
inputValue={targetGroupInput}
144-
name="targetGroup"
145-
onChange={setSelectedTargetGroups}
152+
id="targetGroup"
146153
options={targetGroups}
147-
setInputValue={setTargetGroupInput}
148-
showSearch={false}
149-
title={t('search.titleDropdownTargetGroup')}
154+
visibleOptions={5.97} // use decimal to make scrollable content visible
155+
texts={{
156+
placeholder: t('search.titleDropdownTargetGroup'),
157+
}}
150158
value={selectedTargetGroups}
159+
onChange={(selectedOptions: Option[]) =>
160+
setSelectedTargetGroups(
161+
selectedOptions.map((option) => option.value)
162+
)
163+
}
164+
theme={{
165+
'--checkbox-background-selected':
166+
'var(--color-input-dark)',
167+
'--checkbox-background-hover': 'var(--color-input-dark)',
168+
'--menu-item-background-color-hover':
169+
'var(--color-input-light)',
170+
'--menu-item-background-color-selected-hover':
171+
'var(--color-input-light)',
172+
}}
173+
multiSelect
174+
noTags
151175
/>
152176
</div>
153177
)}
154178
<div>
155-
<SearchSelect
179+
<Select
156180
id="accessibilityProfile"
181+
className={styles.accessibilityProfileSelector}
157182
texts={{
158-
label: t('search:search.labelAccessibilityProfile'),
159183
placeholder: t('search:search.labelAccessibilityProfile'),
160184
clearButtonAriaLabel_one: t(
161185
'search:search.ariaLabelClearAccessibilityProfile'
162186
),
163187
}}
164188
options={accessibilityProfiles}
189+
visibleOptions={5.97} // use decimal to make scrollable content visible
165190
value={selectedAccessibilityProfile ?? undefined}
166191
onChange={handleAccessibilityProfileOnChange}
167192
icon={<IconPersonWheelchair aria-hidden />}
@@ -174,17 +199,8 @@ export const SimpleSearchForm: React.FC<SearchComponentType> = ({
174199
'var(--color-input-dark)',
175200
'--menu-item-color-selected-hover': 'var(--color-white)',
176201
}}
177-
// FIXME: how to handle no outline?
178-
// noOutline
179-
// FIXME: Using the clearable could be a better solution than offering an empty option,
180-
// but the HDS has a bug or unfinished feature in the clearable
181-
// and the controlled state of the input value, does not work when it's used.
182-
// A following error could be thrown:
183-
// "downshift: A component has changed the uncontrolled prop "selectedItem" to be controlled.
184-
// This prop should not switch from controlled to uncontrolled (or vice versa).
185-
// Decide between using a controlled or uncontrolled Downshift element for the lifetime of
186-
// the component. More info: https://github.com/downshift-js/downshift#control-props".
187-
// clearable
202+
multiSelect={false}
203+
noTags
188204
/>
189205
</div>
190206
<div

apps/sports-helsinki/src/domain/search/combinedSearch/hooks/useFormValues.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,19 @@ export function useFormValues() {
2828
const [selectedSportsCategories, setSelectedSportsCategories] =
2929
React.useState<string[]>(formValues.sportsCategories);
3030
const [sportsCategoryInput, setSportsCategoryInput] = React.useState('');
31-
const sportsCategories = getSportsCategoryOptions(t).sort(
32-
sortExtendedCategoryOptions
33-
);
31+
const sportsCategories = getSportsCategoryOptions(t)
32+
.sort(sortExtendedCategoryOptions)
33+
.map((option) => ({
34+
...option,
35+
label: option.text,
36+
}));
3437
const [selectedTargetGroups, setSelectedTargetGroups] = React.useState<
3538
string[]
3639
>(formValues.targetGroups);
37-
const [targetGroupInput, setTargetGroupInput] = React.useState('');
38-
const targetGroups = getTargetGroupOptions(t);
40+
const targetGroups = getTargetGroupOptions(t).map((option) => ({
41+
...option,
42+
label: option.text,
43+
}));
3944
const accessibilityProfiles = [
4045
// Empty option is needed with the HDS-select because of a bug in it.
4146
// Otherwise a following error is thrown and the value handling does not work properly,
@@ -74,8 +79,6 @@ export function useFormValues() {
7479
sportsCategories,
7580
selectedTargetGroups,
7681
setSelectedTargetGroups,
77-
targetGroupInput,
78-
setTargetGroupInput,
7982
targetGroups,
8083
accessibilityProfiles,
8184
selectedAccessibilityProfile,

apps/sports-helsinki/src/domain/search/combinedSearch/search.module.scss

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -47,22 +47,6 @@ $buttonWidth: 180px;
4747
margin-bottom: var(--spacing-xl);
4848
}
4949
}
50-
51-
.checkbox {
52-
margin-top: var(--spacing-xs);
53-
label {
54-
display: flex;
55-
align-items: center;
56-
color: var(--color-primary-black);
57-
}
58-
@include breakpoints.respond-above(s) {
59-
margin-top: 0;
60-
}
61-
}
62-
63-
.dateSelectorWrapper {
64-
align-self: flex-end;
65-
}
6650
}
6751

6852
.buttonWrapperWithTargetGroupFilter {
@@ -116,13 +100,13 @@ $buttonWidth: 180px;
116100
align-items: center;
117101

118102
grid-template-columns: 1fr;
119-
grid-gap: var(--spacing-m);
103+
grid-gap: var(--spacing-s);
120104

121105
margin-bottom: var(--spacing-xs);
122106

123107
@include breakpoints.respond-above(s) {
124108
grid-template-columns: 1fr;
125-
grid-gap: var(--spacing-xs);
109+
grid-gap: var(--spacing-s);
126110

127111
margin-bottom: var(--spacing-xs);
128112
}
@@ -142,3 +126,50 @@ $buttonWidth: 180px;
142126
padding: 0 0 var(--spacing-xs) 0;
143127
position: relative;
144128
}
129+
130+
.textSearchRow {
131+
align-items: flex-end;
132+
margin-bottom: var(--spacing-2-xs);
133+
134+
@include breakpoints.respond-above(m) {
135+
grid-template-columns: repeat(4, minmax(0, 1fr));
136+
}
137+
138+
& > :first-child {
139+
@include breakpoints.respond-above(s) {
140+
grid-column: 1 / 5;
141+
}
142+
padding: var(--spacing-xs);
143+
144+
svg {
145+
margin-right: var(--spacing-xs);
146+
}
147+
}
148+
149+
.searchInputWrapper {
150+
padding-left: var(--spacing-2-xs);
151+
}
152+
}
153+
.categoriesSelector,
154+
.targetGroupSelector,
155+
.accessibilityProfileSelector {
156+
--icon-size: var(--spacing-layout-xs);
157+
--border-width: 0;
158+
---dropdown-height: var(--spacing-3-xl);
159+
--menu-item-height: var(---dropdown-height);
160+
161+
max-width: unset !important;
162+
height: var(--dropdown-height);
163+
164+
span {
165+
margin-right: 0;
166+
margin-left: var(--spacing-4-xs);
167+
font-size: var(--fontsize-body-m);
168+
line-height: 1.5rem;
169+
}
170+
171+
/* Icon on start of button */
172+
svg {
173+
width: var(--icon-size);
174+
}
175+
}

apps/sports-helsinki/src/domain/search/combinedSearch/searchTabs/SearchTabs.tsx

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
import { EventTypeId, Select } from '@events-helsinki/components';
1+
import {
2+
EventTypeId,
3+
Select,
4+
useSearchTranslation,
5+
} from '@events-helsinki/components';
26
import classnames from 'classnames';
37
import type {
48
ButtonPresetTheme,
59
ButtonProps,
610
ButtonTheme,
7-
SelectCustomTheme,
811
Option,
912
} from 'hds-react';
1013
import { Button, ButtonVariant } from 'hds-react';
@@ -73,6 +76,7 @@ type SearchTabListMobileProps = {
7376
function SearchTabListMobile({ data }: SearchTabListMobileProps) {
7477
const router = useRouter();
7578
const { resultCounts, activeTab, setActiveTab } = useTabsContext();
79+
const { t } = useSearchTranslation();
7680

7781
const options = useMemo(
7882
(): Option[] =>
@@ -105,17 +109,19 @@ function SearchTabListMobile({ data }: SearchTabListMobileProps) {
105109
return (
106110
<div className={styles.tabsMobile}>
107111
<Select
108-
theme={
109-
{
110-
'--menu-item-background': 'var(--color-input-dark)',
111-
'--menu-item-background-hover': 'var(--color-input-dark)',
112-
'--menu-item-background-selected-hover': 'var(--color-input-dark)',
113-
} as SelectCustomTheme
114-
}
115-
texts={{ label: 'venues-search-tabs-mobile' }}
112+
className={styles.tabsMobileSelect}
113+
theme={{
114+
'--menu-item-background-color-hover': 'var(--color-input-light)',
115+
'--menu-item-background-color-selected': 'var(--color-input-dark)',
116+
'--menu-item-background-color-selected-hover':
117+
'var(--color-input-dark)',
118+
'--menu-item-color-selected-hover': 'var(--color-white)',
119+
}}
120+
texts={{ label: t('appSports:search.descriptionSearchTabs') }}
116121
value={activeTab}
117122
onChange={handleSearchTabChange}
118123
options={options}
124+
visibleOptions={5.97} // use decimal to make scrollable content visible
119125
/>
120126
</div>
121127
);

apps/sports-helsinki/src/domain/search/combinedSearch/searchTabs/searchTabs.module.scss

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,30 @@
66
}
77
}
88

9+
.tabsMobileSelect {
10+
--icon-size: var(--spacing-layout-xs);
11+
--border-width: 2px;
12+
--dropdown-border-color-default: var(--color-black);
13+
14+
---dropdown-height: var(--spacing-3-xl);
15+
--menu-item-height: var(---dropdown-height);
16+
17+
max-width: unset !important;
18+
height: var(--dropdown-height);
19+
20+
span {
21+
margin-right: 0;
22+
margin-left: var(--spacing-4-xs);
23+
font-size: var(--fontsize-body-m);
24+
line-height: 1.5rem;
25+
}
26+
27+
/* Icon on start of button */
28+
svg {
29+
width: var(--icon-size);
30+
}
31+
}
32+
933
.tabs {
1034
display: flex;
1135
justify-content: flex-start;

apps/sports-helsinki/src/domain/search/eventSearch/searchResultList/unifiedSearchOrderBySelect.module.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@
66
@include breakpoints.respond-below(s) {
77
margin: 0;
88
width: 100%;
9+
max-width: unset !important;
910
}
1011
}

packages/components/src/components/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ export { default as ScrollIntoViewWithFocus } from './scrollIntoViewWithFocus/Sc
2929
export * from './search';
3030
export { default as Section } from './section/Section';
3131
export { default as Select } from './select/Select';
32-
export { default as SearchSelect } from './select/SearchSelect';
3332
export * from './shareLinks';
3433
export { default as SkeletonLoader } from './skeletonLoader/SkeletonLoader';
3534
export { default as LoadingSpinner } from './spinner/LoadingSpinner';

packages/components/src/components/orderBySelect/eventsOrderBySelect.module.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@
66
@include breakpoints.respond-below(s) {
77
margin: 0;
88
width: 100%;
9+
max-width: unset !important;
910
}
1011
}

0 commit comments

Comments
 (0)