Skip to content

Commit 8ce7ce0

Browse files
committed
Unify search container
Fixes #1269. Introduces a search container component for our search fields. This should make them behave more similarly and thus prevent user confusion. Also now hides the reset button in case the field is empty.
1 parent 68569f8 commit 8ce7ce0

7 files changed

Lines changed: 170 additions & 112 deletions

File tree

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { t } from "i18next";
2+
import ButtonLikeAnchor from "./ButtonLikeAnchor";
3+
4+
5+
const SearchContainer = ({
6+
value,
7+
handleChange,
8+
clearSearchField,
9+
isExpand,
10+
isInModal,
11+
isDisabled,
12+
style,
13+
}: {
14+
value: string
15+
handleChange: (value: string) => unknown
16+
clearSearchField: () => unknown
17+
isExpand?: boolean
18+
isInModal?: boolean
19+
isDisabled?: boolean
20+
style?: object
21+
}) => {
22+
23+
let containerClassName = "search-container";
24+
if (isExpand) {
25+
containerClassName = containerClassName + " expand";
26+
}
27+
if (isInModal) {
28+
containerClassName = containerClassName + " modal";
29+
}
30+
let inputClassName = "search";
31+
if (isDisabled) {
32+
inputClassName = inputClassName + " disabled"
33+
}
34+
if (!value) {
35+
inputClassName = inputClassName + " fullwidth";
36+
}
37+
let buttonClassName = "clear";
38+
if (isDisabled) {
39+
buttonClassName = buttonClassName + " disabled"
40+
}
41+
42+
return (
43+
<div className={containerClassName} style={style}>
44+
{value && <ButtonLikeAnchor
45+
extraClassName={buttonClassName}
46+
onClick={() => clearSearchField()}
47+
disabled={isDisabled}
48+
/>}
49+
<input
50+
type="text"
51+
className={inputClassName}
52+
placeholder={t("TABLE_FILTERS.PLACEHOLDER")}
53+
onChange={(e) => handleChange(e.target.value)}
54+
name="textFilter"
55+
value={value}
56+
disabled={isDisabled}
57+
/>
58+
</div>
59+
);
60+
};
61+
62+
export default SearchContainer;

src/components/shared/TableFilters.tsx

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import DropDown from "./DropDown";
3131
import { AsyncThunk } from "@reduxjs/toolkit";
3232
import ButtonLikeAnchor from "./ButtonLikeAnchor";
3333
import { ParseKeys } from "i18next";
34+
import SearchContainer from "./SearchContainer";
3435

3536
/**
3637
* This component renders the table filters in the upper right corner of the table
@@ -98,6 +99,14 @@ const TableFilters = ({
9899
dispatch(loadResourceIntoTable());
99100
};
100101

102+
const handleSearchChange = (value: string) => {
103+
handleChange("textFilter", value);
104+
}
105+
106+
const clearSearchField = () => {
107+
dispatch(removeTextFilter());
108+
};
109+
101110
// Handle changes when an item of the component is changed
102111
const handleChange = (name: string, value: string) => {
103112
let mustApplyChanges = false;
@@ -233,16 +242,12 @@ const TableFilters = ({
233242
<>
234243
<div className="filters-container">
235244
{/* Text filter - Search Query */}
236-
<div className="search-container">
237-
<input
238-
type="text"
239-
className="search expand"
240-
placeholder={t("TABLE_FILTERS.PLACEHOLDER")}
241-
onChange={(e) => handleChange("textFilter", e.target.value)}
242-
name="textFilter"
243-
value={textFilter}
244-
/>
245-
</div>
245+
<SearchContainer
246+
value={textFilter}
247+
handleChange={handleSearchChange}
248+
clearSearchField={clearSearchField}
249+
isExpand={true}
250+
/>
246251

247252
{/* Selection of filters and management of filter profiles*/}
248253
{/*show only if filters.filters contains filters*/}
@@ -346,12 +351,14 @@ const TableFilters = ({
346351
</div>
347352

348353
{/* Remove icon to clear all filters */}
349-
<ButtonLikeAnchor
350-
onClick={removeFilters}
351-
tooltipText="TABLE_FILTERS.CLEAR"
352-
>
353-
<i className="clear fa fa-times" />
354-
</ButtonLikeAnchor>
354+
{filterMap.some(e => e.value) &&
355+
<ButtonLikeAnchor
356+
onClick={removeFilters}
357+
tooltipText="TABLE_FILTERS.CLEAR"
358+
>
359+
<i className="clear fa fa-times" />
360+
</ButtonLikeAnchor>
361+
}
355362
{/* Settings icon to open filters profile dialog (save and editing filter profiles)*/}
356363
<ButtonLikeAnchor
357364
onClick={() => setFilterSettings(!showFilterSettings)}

src/components/shared/wizard/SelectContainer.tsx

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import cn from "classnames";
44
import { useField } from "formik";
55
import ButtonLikeAnchor from "../ButtonLikeAnchor";
66
import { ParseKeys } from "i18next";
7+
import SearchContainer from "../SearchContainer";
78

89
type Item = {
910
name: string
@@ -67,10 +68,6 @@ const SelectContainer = ({
6768
// eslint-disable-next-line react-hooks/exhaustive-deps
6869
}, []);
6970

70-
const disabledStyle = {
71-
backgroundColor: "#eeeff0",
72-
};
73-
7471
const disabledSelectStyle = {
7572
backgroundColor: "#eeeff0",
7673
};
@@ -205,20 +202,13 @@ const SelectContainer = ({
205202
</label>
206203
{/*Search*/}
207204
{resource.searchable && (
208-
<div className="search-container">
209-
{/* search bar */}
210-
<ButtonLikeAnchor extraClassName="clear" onClick={() => clearSearchField()} />
211-
<input
212-
type="text"
213-
id="search"
214-
className="search"
215-
disabled={!manageable}
216-
style={manageable ? {} : disabledStyle}
217-
placeholder={t("TABLE_FILTERS.PLACEHOLDER")}
218-
onChange={(e) => handleChangeSearch(e.target.value)}
219-
value={searchField}
220-
/>
221-
</div>
205+
<SearchContainer
206+
value={searchField}
207+
handleChange={handleChangeSearch}
208+
clearSearchField={clearSearchField}
209+
isDisabled={!manageable}
210+
style={{ marginTop: "10px" }}
211+
/>
222212
)}
223213
{/*Select with options provided by backend*/}
224214
<select

src/components/users/partials/wizard/UserEffectiveRolesTab.tsx

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import React, { useState } from "react";
33
import { useTranslation } from "react-i18next";
44
import ButtonLikeAnchor from "../../../shared/ButtonLikeAnchor";
55
import ModalContent from "../../../shared/modals/ModalContent";
6+
import SearchContainer from "../../../shared/SearchContainer";
67

78
/**
89
* This component renders the effective role tab of the user details modal
@@ -44,17 +45,11 @@ const UserEffectiveRolesTab = <T extends RequiredFormProps>({
4445
<p>{t("USERS.USERS.DETAILS.DESCRIPTION.EFFECTIVEROLES")}</p>
4546

4647
{/* list all roles a user got */}
47-
<div className="search-container">
48-
<ButtonLikeAnchor extraClassName="clear" onClick={() => clearSearchField()} />
49-
<input
50-
type="text"
51-
id="search_effective"
52-
className="search"
53-
value={searchField}
54-
onChange={(e) => handleChangeSearch(e.target.value)}
55-
placeholder={t("TABLE_FILTERS.PLACEHOLDER")}
56-
/>
57-
</div>
48+
<SearchContainer
49+
value={searchField}
50+
handleChange={handleChangeSearch}
51+
clearSearchField={clearSearchField}
52+
/>
5853

5954
<select multiple style={{ height: "26em" }}>
6055
{items.map((item, key) => (

src/styles/components/_inputs.scss

Lines changed: 71 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
@use "../base/fontawesome/fa-mixins";
33
@use "../base/fontawesome/variables" as variables2;
44
@use "../base/variables";
5+
@use "../mixins/button";
56

67
/**
78
* Licensed to The Apereo Foundation under one or more contributor license
@@ -82,21 +83,6 @@ textarea {
8283
background-position: 19px center;
8384
}
8485

85-
&.search,
86-
&#search {
87-
appearance: none;
88-
vertical-align: top;
89-
padding: 0 20px 0 40px !important;
90-
height: 40px;
91-
92-
&.expand {
93-
transition: width 0.2s ease-in;
94-
95-
&:focus {
96-
width: 200px;
97-
}
98-
}
99-
}
10086

10187
&.small {
10288
padding: 15px;
@@ -177,14 +163,83 @@ input[type="radio"].ios {
177163
width: 100%;
178164
}
179165

166+
180167
.search-container {
181-
@include fa-mixins.fa-icon(variables2.$fa-var-search, after, inline, 0, 0, inherit, 13px);
168+
width: 100% !important;
169+
height: 40px;
182170
position: relative;
183171

172+
@include fa-mixins.fa-icon(variables2.$fa-var-search, after, inline, 0, 0, inherit, 13px);
173+
174+
outline: variables.$thin-border-stroke color.adjust(variables.$main-border-color, $lightness: -2%);
175+
border-radius: variables.$main-border-radius;
176+
177+
input.search {
178+
border: 0;
179+
border-right-width: 0;
180+
float: left;
181+
margin: 0;
182+
183+
appearance: none;
184+
vertical-align: top;
185+
padding: 0 0px 0 40px !important;
186+
height: 40px;
187+
188+
outline: none;
189+
190+
width: calc(100% - 46px) !important;
191+
192+
&.fullwidth {
193+
width: 100% !important;
194+
}
195+
}
196+
197+
198+
&:focus-within {
199+
outline: 3px solid #378dd4;
200+
}
201+
202+
&.expand {
203+
width: 170px !important;
204+
transition: width 0.2s ease-in;
205+
206+
&:focus-within {
207+
width: 240px !important;
208+
// width: calc(100% + 46px) !important;
209+
}
210+
}
211+
212+
button.clear {
213+
height: 40px;
214+
width: 46px;
215+
float: right;
216+
@include button.btn(white);
217+
@include fa-mixins.fa-icon(variables2.$fa-var-times, before, block, 0, 0.45em 0.45em 0.55em 0.45em, inherit, 14px);
218+
opacity: 1 !important;
219+
220+
&, &:hover, &:focus, &:active {
221+
background: none;
222+
background-color: #fff;
223+
border: 0;
224+
box-shadow: none;
225+
}
226+
227+
&.disabled, &.disabled:hover, &.disabled:focus, &.disabled:active {
228+
background-color: #eeeff0,
229+
}
230+
231+
&+input.search {
232+
width: calc(100% - 46px) !important;
233+
}
234+
235+
236+
}
237+
184238
&:after {
185239
position: absolute;
186240
top: 13px;
187241
left: 10px;
188242
}
243+
189244
}
190245

src/styles/components/_multi-select.scss

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -75,31 +75,6 @@
7575
display: block;
7676
float: left;
7777
vertical-align: top;
78-
79-
.search-container {
80-
width: 100% !important;
81-
margin-top: 10px;
82-
}
83-
84-
input.search {
85-
border-radius: variables.$main-border-radius 0 0 variables.$main-border-radius;
86-
border-right-width: 0;
87-
float: left;
88-
margin: 0;
89-
}
90-
91-
button.clear {
92-
height: inherit;
93-
float: right;
94-
@include button.btn(white);
95-
@include fa-mixins.fa-icon(variables2.$fa-var-times, before, block, 0, 0.45em 0.45em 0.55em 0.45em, inherit, 14px);
96-
97-
border-radius: 0 variables.$main-border-radius variables.$main-border-radius 0;
98-
99-
&+input.search {
100-
width: calc(100% - 46px) !important;
101-
}
102-
}
10378
}
10479
}
10580

src/styles/components/modals/_modal-base.scss

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -136,32 +136,6 @@
136136

137137
.modal-body {
138138
padding: 3%;
139-
140-
.search-container {
141-
width: 100% !important;
142-
margin-top: 5px;
143-
}
144-
145-
input.search {
146-
border-radius: variables.$main-border-radius 0 0 variables.$main-border-radius;
147-
border-right-width: 0;
148-
float: left;
149-
margin: 0;
150-
}
151-
152-
button.clear {
153-
height: inherit;
154-
width: 46px;
155-
float: right;
156-
@include button.btn(white);
157-
@include fa-mixins.fa-icon(variables2.$fa-var-times, before, block, 0, 0.45em 0.45em 0.55em 0.45em, inherit, 14px);
158-
159-
border-radius: 0 variables.$main-border-radius variables.$main-border-radius 0;
160-
161-
&+input.search {
162-
width: calc(100% - 46px) !important;
163-
}
164-
}
165139
}
166140

167141
.modal {

0 commit comments

Comments
 (0)