Skip to content

Commit

Permalink
Improvements to InputMultiselect component
Browse files Browse the repository at this point in the history
  • Loading branch information
julianguyen committed Feb 20, 2024
1 parent 2680102 commit d012f3a
Show file tree
Hide file tree
Showing 29 changed files with 236 additions and 231 deletions.
16 changes: 1 addition & 15 deletions app/assets/stylesheets/application/shared.scss
Original file line number Diff line number Diff line change
Expand Up @@ -100,26 +100,12 @@
display: flex;
flex-direction: row;
margin-left: $size-8;
width: 30%;

@media screen and (max-width: $large) {
width: 40%;
}
width: 25%;

@media screen and (max-width: $medium) {
flex-direction: column;
margin-left: $size-0;
width: 100%;
}

div[class*="checkboxLabel"]{
flex-grow: 1;
margin: $size-0 $size-8 $size-0 $size-8;

@media screen and (max-width: $medium) {
margin: $size-2 $size-0 $size-0 $size-8;
}
}

}
}
19 changes: 9 additions & 10 deletions app/controllers/concerns/collection_page_setup_concern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,19 @@ module CollectionPageSetupConcern

def page_collection(collection, model_name, filters_model = {})
name = params[:search]
selected_filters = params[:filters]
search_filters = params[:filters]
model = Object.const_get(model_name.capitalize)
search = model.where(
'name ilike ? AND user_id = ?',
"%#{name}%",
current_user.id
).all
user = model.where(user_id: current_user.id)
if selected_filters.present?
user = apply_filters(user, selected_filters, filters_model[:filters])
if search_filters.present?
user = apply_filters(user, search_filters, filters_model[:filters])
end
setup_collection(collection, user, search, name)
@options_for_multiselect = filters_model[:options]
@multiselect_checkboxes = filters_model[:checkboxes]
@page_new = t("#{model_name.pluralize}.new")
end

Expand All @@ -34,12 +34,11 @@ def setup_collection(collection, user, search, name)

private

def apply_filters(user, selected_filters, filters_model)
filters_list = selected_filters.split(',')

filters_list.each do |filter|
user = user.where(filters_model[filter.to_i])
def apply_filters(user, search_filters, filters_model)
filtered_user = user
search_filters.each do |filter|
filtered_user = filtered_user.where(filters_model[filter])
end
user
filtered_user.count > 0 ? filtered_user : user
end
end
2 changes: 1 addition & 1 deletion app/controllers/moments_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class MomentsController < ApplicationController
# GET /moments
# GET /moments.json
def index
page_collection('@moments', 'moment', hash_for_multiselect)
page_collection('@moments', 'moment', multiselect_hash)
respond_to do |format|
format.json { render json: moments_data_json }
format.html { moments_data_html }
Expand Down
12 changes: 6 additions & 6 deletions app/controllers/search_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ def index

def posts
data_type = params[:search][:data_type]
term = params[:search][:name]
selected_filters = params[:selected_filters]
name = params[:search][:name]
search_filters = params[:search][:filters]

return unless data_type.in?(%w[moment category mood strategy medication])

redirect_to_path(make_path(term, data_type, selected_filters))
redirect_to_path(make_path(name, data_type, search_filters))
end

private
Expand All @@ -29,10 +29,10 @@ def search_by_email(email)
.where.not(banned: true)
end

def make_path(term, data_type, selected_filters)
def make_path(name, data_type, search_filters)
search_hash = {}
search_hash[:search] = term if term.present?
search_hash[:filters] = selected_filters if selected_filters.present?
search_hash[:search] = name if name.present?
search_hash[:filters] = search_filters if search_filters.present?

send("#{data_type.pluralize}_path", search_hash)
end
Expand Down
55 changes: 32 additions & 23 deletions app/helpers/moments_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ def get_resources_data(moment, current_user)
show_crisis_prevention: r.nil? ? false : r.show_crisis_prevention }
end

def hash_for_multiselect
{ options: options_for_multiselect, filters: filters_of_multiselect }
def multiselect_hash
{ checkboxes: multiselect_checkboxes, filters: multiselect_filters }
end

private
Expand Down Expand Up @@ -147,27 +147,36 @@ def get_share_link_info(is_strategy, element)
t('moments.secret_share.link_info')
end

def filters_of_multiselect
['secret_share_identifier IS NOT NULL', { viewers: [] },
'length(viewers) > 0', { comment: true },
{ published_at: nil }, 'published_at IS NOT NULL']
end

def options_for_multiselect
options = [t('moments.filters.secret_share'),
t('moments.filters.no_viewers'),
t('moments.filters.one_viewer'),
t('moments.filters.coments_enabled'),
t('moments.filters.draft_enabled'),
t('moments.filters.published')]
options_hash = []
options.each_with_index do |element, index|
options_hash.push({
id: element,
label: element,
value: index
})
def multiselect_filters
{
"secret_share" => 'secret_share_identifier IS NOT NULL',
"no_viewers" => { viewers: [] },
"one_viewer" => 'length(viewers) > 0',
"comments_enabled" => { comment: true },
"draft_enabled" => { published_at: nil },
"published" => 'published_at IS NOT NULL'
}
end

def is_checked(value)
if params[:filters]
return params[:filters].include?(value)
end
false
end

def multiselect_checkboxes
values = ['secret_share', 'no_viewers', 'one_viewer', 'comments_enabled', 'draft_enabled', 'published']
checkboxes = []
values.each_with_index do |value, index|
checkboxes.push({
id: "search_filters_#{index}",
name: "search[filters][]",
label: I18n.t("moments.filters.#{value}"),
value: value,
checked: is_checked(value)
})
end
options_hash
checkboxes
end
end
7 changes: 3 additions & 4 deletions app/views/search/_posts.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@
<div class="searchInputMultiSelect">
<%= react_component 'Input', props: {
type: 'multiSelect',
ariaLabel: t('search.filter_by'),
id: 'selected_filters',
id: 'search_filters',
name: 'search[filters]',
label: t('search.filter_by'),
name: 'selected_filters',
options: @options_for_multiselect
checkboxes: @multiselect_checkboxes
} %>
</div>
<% end %>
Expand Down
22 changes: 22 additions & 0 deletions client/app/components/Input/Input.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,28 @@
}
}

.select select {
@include setFontSize($size-18);
@include setPadding($size-24, $size-0, $size-24, $size-24);
@include paddingForSelectIcon($size-24);

color: $white;
font-weight: $font-weight-400;
width: 100%;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
box-sizing: border-box;
background-color: $white-10;
border-radius: $size-4;
box-shadow: $size-0 $size-2 $size-10 $black-10;
border: none;

&:focus {
background-color: $white-20;
}
}

.select {
position: relative;

Expand Down
97 changes: 51 additions & 46 deletions client/app/components/Input/InputMultiSelect.jsx
Original file line number Diff line number Diff line change
@@ -1,79 +1,84 @@
// @flow
import React, { useState } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import renderHTML from 'react-render-html';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCaretUp, faCaretDown} from '@fortawesome/free-solid-svg-icons';
import css from './InputMultiSelect.scss';
import Input from 'components/Input';
import type { Option } from './utils';
import type { Checkbox } from './utils';
import { InputCheckbox } from 'components/Input/InputCheckbox';
import inputCss from './Input.scss';

export type Props = {
id: string,
name?: string,
ariaLabel?: string,
label?: string,
value?: any,
checkboxes: Checkbox[],
};

export function InputMultiSelect({
id,
name,
options,
ariaLabel,
label,
value: propValue,
checkboxes,
label
}: Props) {
const [value, setValue] = useState([]);
const [opened, setOpened] = useState<boolean>(false);
const ref = useRef(null);

const toggleValue = (e: SyntheticEvent<HTMLInputElement>) => {
const checked = e.target.closest(`.${css.panel}`).querySelectorAll('input:checked');
let actualValues = [];

checked.forEach(check => {
actualValues.push(check.value);
});
setValue(actualValues);
const handleOnClick = () => {
setOpened(!opened);
};

const checkChild = (e: SyntheticEvent<HTMLInputElement>) => {
let checkBox = e.target.closest(`.${css.checkMultiSelect}`).querySelector('[type=checkbox]');
if (e.target.type !== 'checkbox') checkBox.checked = !checkBox.checked;
toggleValue(e);
};
useEffect(() => {
const handleClickOutside = (event) => {
if (ref.current && !ref.current.contains(event.target)) {
setOpened(false);
}
}

const toggleOpen = () => {
setOpened(!opened);
};
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [ref]);

return (
<>
<input type='hidden' value={value} name={name} id={id} role='componentValue'></input>
<div ref={ref}>
<button
className={`${css.buttonL} ${css.fullWidth}`}
type='button'
onClick={toggleOpen}
className={`${css.buttonDarkL} ${css.multiSelectButton}`}
type="button"
onClick={handleOnClick}
id={id}
>
{label || ariaLabel} <FontAwesomeIcon icon={opened ? faCaretUp : faCaretDown }/>
<div>
{label}
</div>
<div>
<FontAwesomeIcon icon={opened ? faCaretUp : faCaretDown }/>
</div>
</button>
<div className={css.panelParent}>
<div className={`${css.panel} ${opened ? '' : css.hidden}`}>
{options.map((option) => (
<span key={option.label} className={css.checkMultiSelect} onClick={checkChild}>
<Input
dark
id={option.id}
label={option.label}
large
name={option.id}
type='checkbox'
uncheckedValue={0}
value={option.value}
/>
</span>
<div
data-testid="multiSelectCheckboxes"
aria-labelledby={id}
className={css.multiSelectCheckboxesWrapper}
role="listbox"
style={{ display: opened ? 'block' : 'none '}}
>
<div className={css.multiSelectCheckboxes}>
{checkboxes.map((checkbox) => (
<InputCheckbox
id={checkbox.id}
name={checkbox.name}
key={checkbox.id}
value={checkbox.value}
checked={checkbox.checked}
uncheckedValue={checkbox.uncheckedValue}
label={checkbox.label}
/>
))}
</div>
</div>
</>
</div>
);
}
Loading

0 comments on commit d012f3a

Please sign in to comment.