-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sage dynamic select input - select2 #709
Changes from 11 commits
d2e0895
0ca1a81
df17301
f9cc302
92f1734
d86ef39
efb27eb
e3529fd
c4cb6b0
b898bd7
707795e
df2baed
7b14a8a
015ebc2
ed386e1
0fbd075
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
class SageDynamicSelect < SageComponent | ||
set_attribute_schema({ | ||
url: String, | ||
label: String, | ||
name: String, | ||
id: String, | ||
has_error: [:optional, TrueClass], | ||
message: [:optional, String], | ||
default_value: [:optional, String, Integer], | ||
default_text: [:optional, String], | ||
paginate: [:optional, TrueClass], | ||
search: [:optional, TrueClass], | ||
clear: [:optional, TrueClass], | ||
placeholder: [:optional, String], | ||
theme: [:optional, String], | ||
required: [:optional, TrueClass], | ||
}) | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
|
||
<div class=" | ||
sage-dynamic-select | ||
<%= "sage-select--error" if component.has_error %> | ||
<%= component.generated_css_classes %>"> | ||
<% if component.theme == 'sage' %> | ||
<label class="sage-select__label" for="<%= component.id %>"><%= component.label || component.id %></label> | ||
<% end %> | ||
<select | ||
class="sage-dynamic-select__data" | ||
<%= component.generated_html_attributes.html_safe %> | ||
name="<%= component.name %>" | ||
id="<%= component.id %>" | ||
tabindex="-1" | ||
aria-hidden="true" | ||
data-js-dynamic-select="true" | ||
data-url=<%= component.url %> | ||
data-default-value="<%= component.default_value if component.default_value.present? %>" | ||
teenwolfblitzer marked this conversation as resolved.
Show resolved
Hide resolved
|
||
data-default-text="<%= component.default_text if component.default_text.present? %>" | ||
data-paginate="<%= component.paginate if component.paginate.present? %>" | ||
data-search="<%= component.search if component.search.present? %>" | ||
data-clear="<%= component.clear if component.clear.present? %>" | ||
data-placeholder="<%= component.placeholder || component.label || component.id %>" | ||
data-theme="<%= component.theme if component.theme.present? %>" | ||
<%= 'required' if component.required.present? %> | ||
> | ||
</select> | ||
<div class="sage-select__message"><%= component.message %></div> | ||
</div> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
$-dynamic-select-default-height: rem(44); | ||
$-dynamic-select-default-padding: rem(4.4) sage-spacing(sm) 0; | ||
teenwolfblitzer marked this conversation as resolved.
Show resolved
Hide resolved
|
||
$-dynamic-select-border-color: sage-color(gray, 400); | ||
$-dynamic-select-placeholder-color: sage-color(gray, 400); | ||
$-dynamic-select-color-success: map-get($sage-field-colors, success); | ||
$-dynamic-select-border-box-shadow-size: map-get($sage-field-configs, box-shadow-size); | ||
$-dynamic-select-selected-height: rem(52); | ||
$-dynamic-select-selected-padding: rem(10.8) sage-spacing(sm) 0; | ||
$-dynamic-select-selected-tag-color: sage-color(primary, 400); | ||
$-dynamic-select-selected-tag-background-color: sage-color(primary, 100); | ||
$-dynamic-select-selected-tag-border-radius: sage-border(radius-x-large); | ||
$-dynamic-select-selected-tag-padding: 0 sage-spacing(xs); | ||
$-dynamic-select-selected-tag-height: rem(30); | ||
$-dynamic-select-clear-font-weight: sage-font-weight(regular); | ||
$-dynamic-select-open-arrow: rotate(180deg) scaleX(-1); | ||
|
||
// empty state // | ||
.sage-dynamic-select { | ||
position: relative; | ||
&__data { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we please move this out onto its own? We tend to avoid this type of use of .sage-dynamic-select {
// ...
}
.sage-dynamic-select__data {
// ...
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For clarity, the goal is to avoid using ❌ .block {
&__element {
// stuff
}
&--variant {
&__element {
// stuff
}
}
} ✅ .block__element {
// stuff
.block--variant & {
// stuff
}
&::after {
// stuff
}
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And with the BEM naming we'd typically not nest an element selector inside a block selector so that the styles contained in a selector are ideally applied to that actual thing. If specificity or context is needed you bascially flip it so instead of: .block {
&--variant {
&__element {
// stuff
}
}
} You do: .block__element {
// stuff
.block--variant & {
// stuff
}
} It is more typing (increased chance for typos 😬 ) but helps ensure each selector block is explicitly applying to that thing, thus preventing style leaks. Everything is also in one place for that particular thing too. Hope this makes sense. 👍 but if not please let me know and we can sync up. 🙌 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done :) |
||
bottom: sage-spacing(xs); | ||
left: 50%; | ||
} | ||
.select2-container--sage { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this need to be nested for sufficient specificity, or could this be pulled out to avoid the extra nesting depth? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done 👍 |
||
.sage-select__arrow::before { | ||
top: rem(3); | ||
} | ||
|
||
.select2-dropdown--below { | ||
margin-top: sage-spacing(xs); | ||
} | ||
|
||
.select2-selection--single.sage-select__field { | ||
height: $-dynamic-select-default-height; | ||
padding: $-dynamic-select-default-padding; | ||
|
||
} | ||
|
||
.select2-selection--single.sage-select__field .select2-selection__placeholder { | ||
color: $-dynamic-select-placeholder-color; | ||
} | ||
.select2-selection--single.sage-select__field .select2-selection__clear { | ||
float: right; | ||
} | ||
.select2-selection--single.sage-select__field .select2-selection__arrow b { | ||
display: none; | ||
} | ||
|
||
// open state // | ||
&.select2-container--open .select2-selection--single.sage-select__field { | ||
border-color: $-dynamic-select-color-success; | ||
box-shadow: $-dynamic-select-border-box-shadow-size $-dynamic-select-color-success; | ||
} | ||
|
||
&.select2-container--open .sage-select__arrow::before { | ||
transform: $-dynamic-select-open-arrow; | ||
} | ||
|
||
&.select2-container--open .sage-select__arrow::before, | ||
&:hover .sage-select__arrow::before { | ||
color: $-dynamic-select-color-success; | ||
} | ||
|
||
// focused state // | ||
&.select2-container--focus .select2-selection--single.sage-select__field { | ||
border-color: $-dynamic-select-border-color; | ||
} | ||
} | ||
} | ||
|
||
// selected state // | ||
.sage-dynamic-select.sage-select--value-selected .select2-container--sage { | ||
.sage-select__arrow::before { | ||
top: rem(6); | ||
} | ||
.select2-selection--single.sage-select__field { | ||
height: $-dynamic-select-selected-height; | ||
padding: $-dynamic-select-selected-padding; | ||
border-color: $-dynamic-select-border-color; | ||
.select2-selection__rendered { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here... if not needed, can this be pulled out a layer or more to avoid the deep nesting? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 |
||
@extend %t-sage-body-small-semi; | ||
display: inline-flex; | ||
flex-direction: row-reverse; | ||
align-items: center; | ||
height: $-dynamic-select-selected-tag-height; | ||
padding: $-dynamic-select-selected-tag-padding; | ||
color: $-dynamic-select-selected-tag-color; | ||
background-color: $-dynamic-select-selected-tag-background-color; | ||
border-radius: $-dynamic-select-selected-tag-border-radius; | ||
} | ||
.select2-selection__rendered .select2-selection__clear { | ||
font-weight: $-dynamic-select-clear-font-weight; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
Sage.dynamicSelect = (function() { | ||
kajabijamell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
function init(el) { | ||
this.data = $(el); | ||
this.opts = { | ||
// Sets the url for the dynamic dropdown | ||
url: data.data('url'), | ||
// Enable pagination, the dropdown will infinitely scroll until the result | ||
// set returns 0 results. | ||
paginate: data.data('paginate'), | ||
// Enables typeahead search for the resource, passing a "search" param in | ||
// the requested url | ||
search: data.data('search'), | ||
// Adds a clear button so selection can be cleared/set back to the blank state | ||
clear: data.data('clear'), | ||
// Sets the placeholder for the dynamic dropdown | ||
placeholder: data.data('placeholder'), | ||
// Sets the theme to default to bootstrap/sage | ||
theme: data.data('theme'), | ||
// Sets the width of the dynamic dropdown | ||
width: "100%", | ||
// Chooses which attribute to use for setting the value of the given select | ||
// box, by default this will be the "id", but sometimes is is useful to set | ||
// it to some other attribute | ||
valueAttribute: "id", | ||
}; | ||
|
||
this.select2 = data.select2({ | ||
minimumResultsForSearch: this.opts.search ? 0 : Infinity, | ||
theme: this.opts.theme, | ||
width: this.opts.width, | ||
placeholder: this.opts.placeholder, | ||
allowClear: this.opts.clear, | ||
ajax: { | ||
delay: 500, | ||
url: function () { | ||
return $(data).data("url"); | ||
}, | ||
cache: true, | ||
data: function (params) { | ||
return { | ||
search: params.term, | ||
page: params.page | ||
}; | ||
}, | ||
processResults: function (data, params) { | ||
var results = data.results ? data.results : data; | ||
formattedData = $.map(results, function (obj) { | ||
obj.id = obj[this.opts.valueAttribute]; | ||
return obj; | ||
}.bind(this)); | ||
|
||
params.page = params.page || 1; | ||
$(data).select2.data = formattedData; | ||
var showMore = data.totalCount ? (params.page * 25) < data.totalCount : true; | ||
kajabijamell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return { | ||
results: formattedData, | ||
pagination: { more: this.opts.paginate && results.length !== 0 && showMore } | ||
}; | ||
}.bind(this) | ||
} | ||
}); | ||
|
||
this.$container = this.data.next(); | ||
|
||
const { $container, select2 } = this | ||
|
||
if (data.data('theme') === 'sage') { | ||
// add sage class | ||
$(select2).data('select2').$selection.addClass("sage-select__field"); | ||
// add arrow icon | ||
const iconTemplate = '<i class="sage-select__arrow" aria-hidden="true"></i>'; | ||
$container.append(iconTemplate); | ||
} | ||
|
||
// set a default option for the input | ||
const defaultValue = data.data('defaultValue'); | ||
const defaultText = data.data('defaultText'); | ||
const newOption = new Option(defaultText, defaultValue, false, false); | ||
$(select2).append(newOption).trigger('change'); | ||
|
||
// add event listeners | ||
$(select2).on('select2:open', function (e) { | ||
// callback for when select box is opened | ||
}); | ||
$(select2).on('select2:close', function (e) { | ||
// callback for when select box is closed | ||
}); | ||
|
||
// function for updating input on change // | ||
const updateInput = function() { | ||
const selectedValue = $(this.data).val(); | ||
const classActive = 'sage-select--value-selected'; | ||
const selectWrapper = $($container).parent(); | ||
const selectLabel = $($container).parent().find('.sage-select__label'); | ||
|
||
Sage.util.isEmptyString(selectedValue) ? selectWrapper.removeClass(classActive) : selectWrapper.addClass(classActive) | ||
Sage.util.isEmptyString(selectedValue) ? selectLabel.hide() : selectLabel.fadeIn(200); | ||
$(this.data).focusout(); | ||
}.bind(this); | ||
|
||
// input change event listener | ||
$(this.data).on('change', function(){ updateInput() }); | ||
|
||
updateInput(); | ||
} | ||
|
||
function unbind() { | ||
kajabijamell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
return { | ||
init, | ||
unbind, | ||
} | ||
})(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note for the group (no actionable items as of yet): we'll have to be mindful of this and the
aria-hidden="true"
below for accessibility. Select2 has been actively working on improving their compliance, but it's not quite there, so we'll have to keep a close watch on it.