Skip to content

Commit

Permalink
Move filter select functionality to select with multiple choices (#276)
Browse files Browse the repository at this point in the history
* Fix undefined helper text parameter.

* Developlement

* Cap icon size to md. Add class to icons.

* Add device detection support

* Add required attribute.

* feat: add attribute maxSelections

* feat: add clear button to select

* feat: accesibility tabbing for select component

* fix: Select component aria states

* fix: adapt select component for safari/ios

---------

Co-authored-by: Sebastian Thulin <sebastian.thulin@helsingborg.se>
Co-authored-by: Thor Brink <thor.brink@helsingborg.se>
Co-authored-by: NiclasNorin <103985736+NiclasNorin@users.noreply.github.com>
  • Loading branch information
4 people authored Aug 23, 2023
1 parent 52ad2f4 commit 2bd1c33
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 45 deletions.
91 changes: 81 additions & 10 deletions source/php/Component/Select/Select.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,58 @@ class Select extends \ComponentLibrary\Component\BaseController
{
public function init()
{

//Extract array for eazy access (fetch only)
extract($this->data);

// Must include a id.
if (!$id) {
$id = uniqid();
}
$this->data['attributeList']['id'] = "select_{$id}";
//Create an random id if not assigned to the component.
$this->data['id'] = $id = $id ? $id : uniqid();

//Set icon size
$this->data['iconSize'] = $this->getIconSize($size);

//Declare select attribute list, use id predefined.
$this->data['selectAttributeList'] = [
'id' => 'select_' . $this->data['id'],
'data-js-select-element' => true
];

//Assign id
$this->data['attributeList']['id'] = "{$id}";
$this->data['attributeList']['data-js-toggle-item'] = $id . "-open-dropdown";
$this->data['attributeList']['data-js-toggle-class'] = 'is-open';
$this->data['attributeList']['data-js-select-component'] = 'true';
$this->data['attributeList']['data-js-click-away'] = 'is-open';
$this->data['attributeList']['data-js-is-empty-select'] = 'true';
$this->data['attributeList']['data-js-device-detect'] = 'true';


//Set general classes
$this->data['classList'][] = $this->getBaseClass($size, true);

if ($multiple) {
$this->data['attributeList']['multiple'] = 'multiple';
$this->data['isMultiSelect'] = true;
$this->data['classList'][] = $this->getBaseClass('multiselect', true);

$this->data['selectAttributeList']['multiple'] = 'multiple';
$this->data['selectAttributeList']['data-js-select-max'] = !empty($maxSelections) && is_numeric($maxSelections) ? $maxSelections : -1;
$this->data['attributeList']['data-js-select-type'] = 'multiple';

$this->data['itemStateIcons'] = (object) [
'active' => 'check_box',
'inactive' => 'check_box_outline_blank',
];
}

if(!$multiple) {
$this->data['isMultiSelect'] = false;
$this->data['classList'][] = $this->getBaseClass('singleselect', true);

$this->data['attributeList']['data-js-select-type'] = 'single';

$this->data['itemStateIcons'] = (object) [
'active' => 'radio_button_checked',
'inactive' => 'radio_button_unchecked',
];
}

$this->data['intersection'] = [];
Expand All @@ -32,16 +70,49 @@ public function init()
}

if ($name) {
$this->data['attributeList']['name'] = $name;
$this->data['selectAttributeList']['name'] = $name;
}

if ($errorMessage) {
$this->data['data-invalid-message'] = $errorMessage;
$this->data['classList'][] = "has-invalid-message";
}

if ($required) {
$this->data['attributeList']['required'] = 'required';
$this->data['attributeList']['data-required'] = '1';
$this->data['selectAttributeList']['required'] = 'required';
$this->data['selectAttributeList']['aria-hidden'] = 'true';
$this->data['selectAttributeList']['data-required'] = '1';
$this->data['selectAttributeList']['aria-required'] = '1';

$this->data['attributeList']['data-js-required'] = 'true';

$this->data['classList'][] = "is-required";
}

$this->data['selectAttributes'] = self::buildAttributes(
$this->data['selectAttributeList']
);

$this->data['clearButtonEnabled'] = !$multiple && !$required;

//Determine if this is selected
$this->data['isSelected'] = function($key, $boolean = true)
{
if( $this->data['preselected'] === $key ) {
return $boolean ? true : 'selected';
}
if(array_key_exists($key, $this->data['intersection'])) {
return $boolean ? true : 'selected';
}
return $boolean ? false : '';
};
}

private function getIconSize($fieldSize = 'md'): string
{
if($fieldSize == 'lg') {
return 'md';
}
return $fieldSize;
}
}
6 changes: 6 additions & 0 deletions source/php/Component/Select/partials/action.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<div class="c-field__inner">
<div tabindex="0" data-select-preserve-native-behavior></div>
<div class="{{ $baseClass }}__action-overlay" tabindex="0" data-js-select-action-overlay data-js-toggle-trigger="{{$id}}-open-dropdown" data-js-placeholder="{{ $placeholder && $isMultiSelect ? $placeholder : '' }}">
{{ $placeholder && $isMultiSelect ? $placeholder : '' }}
</div>
</div>
9 changes: 9 additions & 0 deletions source/php/Component/Select/partials/clear.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@button([
'style' => 'basic',
'icon' => 'close',
'size' => 'md',
'color' => 'secondary',
'classList' => [$baseClass . '__clear-button'],
'attributeList' => ['data-js-select-clear' => '1']
])
@endbutton
34 changes: 34 additions & 0 deletions source/php/Component/Select/partials/dropdown.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!-- Visual dropdown list -->
<div class="{{$baseClass}}__dropdown" data-js-dropdown-element="true" aria-hidden="true">
<ul class="{{$baseClass}}__options u-unlist u-padding--0 u-margin--0" role="listbox">
@foreach ($options as $value => $name)
<li class="{{$baseClass}}__option {{ $isSelected($value, true) ? 'is-selected' : '' }}" data-js-dropdown-option="{{$value}}" role="option" aria-selected="{{ $isSelected($value, true) ? 'true' : 'false' }}" tabindex="0">
@icon([
'icon' => $itemStateIcons->inactive,
'size' => $iconSize,
'classList' => [
$baseClass . '__option-icon',
$baseClass . '__unchecked-icon'
],
'attributeList' => [
'aria-hidden' => $isSelected($value, true) ? 'true' : 'false'
]
])
@endicon
@icon([
'icon' => $itemStateIcons->active,
'size' => $iconSize,
'classList' => [
$baseClass . '__option-icon',
$baseClass . '__checked-icon'
],
'attributeList' => [
'aria-hidden' => $isSelected($value, true) ? 'false' : 'true'
]
])
@endicon
<span class="{{$baseClass}}__option-label">{{ $name }}</span>
</li>
@endforeach
</ul>
</div>
8 changes: 8 additions & 0 deletions source/php/Component/Select/partials/error.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<div class="c-select__select-invalid-message" id="error_input_{{ $id }}_message">
@icon([
'icon' => 'error',
'size' => 'sm'
])
@endicon
<span class="errorText"></span>
</div>
16 changes: 16 additions & 0 deletions source/php/Component/Select/partials/expand.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<div class="{{$baseClass}}__expand-button" role="button" >
@icon([
'icon' => 'expand_less',
'size' => 'md',
'classList' => [$baseClass . '__expand-less-icon'],
'attributeList' => ['aria-hidden' => 'true']
])
@endicon
@icon([
'icon' => 'expand_more',
'size' => 'md',
'classList' => [$baseClass . '__expand-more-icon'],
'attributeList' => ['aria-hidden' => 'false']
])
@endicon
</div>
1 change: 1 addition & 0 deletions source/php/Component/Select/partials/focus.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div class="{{ $baseClass }}_focus-styler u-level-top"></div>
70 changes: 39 additions & 31 deletions source/php/Component/Select/select.blade.php
Original file line number Diff line number Diff line change
@@ -1,54 +1,62 @@
<div class="{{ $class }} c-field">
@if ($label)
<label class="c-field__label {{$hideLabel ? 'u-sr__only' : ''}}" for="select_{{ $id }}">{{ $label }}
<div class="c-field {{ $class }}" {!! $attribute !!}>
@if ($label && $hideLabel)
<label class="u-sr__only" for="select_{{ $id }}">
{{ $label }}
</label>
@endif

@if ($label && !$hideLabel)
<label class="c-field__label" for="select_{{ $id }}">
{{ $label }}
@if ($required)
<span class="u-color__text--danger">*</span>
{{--
Field has aria attribute required, this will be read as required.
Aria hidden in place here, to avoid duplicate notations in screenreader.
--}}
<span class="u-color__text--danger" aria-hidden="true">*</span>
@endif
</label>
@endif

@if (!empty($description))
@typography([
'element' => 'div',
'classList' => ['text-sm', 'text-dark-gray']
'classList' => [
$baseClass . '__description',
'text-sm',
'text-dark-gray'
]
])
{{ $description }}
@endtypography
@endif

<div class="u-position--relative">
<select {!! $attribute !!}>
@if ($label && empty($hidePlaceholder))
<option class="c-select__option" value="" {{ $preselected === '' ? 'selected' : '' }}>
{!! $label !!}</option>
<div class="{{ $baseClass }}__field-container">
<select {!! $selectAttributes !!} class="{{ $baseClass }}__select-element" tabindex="-1">

@if ($preselected !== '' && !$isMultiSelect)
<option class="c-select__select-option" value="">{{$placeholder ? $placeholder : ""}}</option>
@endif

@foreach ($options as $key => $name)
<option class="c-select__option" value="{!! $key !!}"
{{ $preselected === $key || isset($intersection[$key]) ? 'selected' : '' }}>
{!! $name !!}</option>
<option class="c-select__select-option" value="{!! $key !!}" {{ $isSelected($key, false) }}>
{!! $name !!}
</option>
@endforeach

{!! $slot !!}
@if(!empty($slot))
{!! $slot !!}
@endif
</select>
<div class="{{ $baseClass }}_focus-styler u-level-top"></div>
@icon([
'classList' => ['c-select__icon'],
'icon' => 'expand_more',
'size' => 'md'
])
@endicon
@include('Select.partials.focus')
@include('Select.partials.expand')
@includeWhen($clearButtonEnabled, 'Select.partials.clear')
@include('Select.partials.action')
</div>
@include('Select.partials.dropdown')
@include('Select.partials.error')

<div class="c-select__select-invalid-message" id="error_input_{{ $id }}_message">
@icon([
'icon' => 'error',
'size' => 'sm'
])
@endicon
<span class="errorText"></span>
</div>
@if ($helperText)
@if (!empty($helperText))
<small class="c-field__helper">{{ $helperText }}</small>
@endif
</div>
14 changes: 10 additions & 4 deletions source/php/Component/Select/select.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"slug": "select",
"default": {
"label": "",
"placeholder": "",
"id": false,
"description": "",
"required": false,
Expand All @@ -11,11 +12,14 @@
"multiple": false,
"name": false,
"hideLabel": false,
"hidePlaceholder": false,
"size": "md"
"helperText": "",
"size": "md",
"maxSelections": false,
"hidePlaceholder": false
},
"description": {
"label": "The placeholder of the dropdown",
"placeholder": "Supported if multiple select is enabled.",
"id": "The id attribute for the select component. Will be prefixed with \"select_\"",
"description": "Additional description or instructions.",
"required": "If the element should be required",
Expand All @@ -24,8 +28,10 @@
"multiple": "Sets the select box to a multiple select",
"name": "The name attribute for the select component",
"hideLabel": "Hides the label",
"hidePlaceholder": "Hides the placeholder but keeps the label",
"size": "The size of the select component (sm, md, lg)"
"helperText": "Adds a helping text, below the element.",
"size": "The size of the select component (sm, md, lg)",
"maxSelections": "The maximum number of selections allowed. Will only be applied if \"multiple\" is true.",
"hidePlaceholder": "Hides the placeholder but keeps the label"
},
"view": "select.blade.php",
"dependency": {
Expand Down

0 comments on commit 2bd1c33

Please sign in to comment.