Skip to content
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

Merged
merged 16 commits into from
Aug 23, 2021
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 18 additions & 0 deletions docs/lib/sage_rails/app/sage_components/sage_dynamic_select.rb
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"
Copy link
Member

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.

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 {
Copy link
Contributor

Choose a reason for hiding this comment

The 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 & in Sage.

.sage-dynamic-select {
  // ...
} 

.sage-dynamic-select__data {
  // ...
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For clarity, the goal is to avoid using & for segments of a particular element selector, but OK to use it for a whole element. So:

.block {
  &__element {
    // stuff
  }
  
  &--variant {
    &__element {
      // stuff
    }
  }
}

.block__element {
  // stuff

  .block--variant & {
    // stuff
  }
  
  &::after {
    // stuff
  }
}

Copy link
Contributor

Choose a reason for hiding this comment

The 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. 🙌

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done :)

bottom: sage-spacing(xs);
left: 50%;
}
.select2-container--sage {
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 {
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
Expand Up @@ -15,7 +15,7 @@ $-select-color-default: map-get($sage-field-colors, default);
$-select-color-error: map-get($sage-field-colors, error);
$-select-color-label-background: map-get($sage-field-colors, label-background);
$-select-color-success: map-get($sage-field-colors, success);
$-select-filled-top-padding: rem(6);
$-select-filled-top-padding: rem(4.4);
kajabijamell marked this conversation as resolved.
Show resolved Hide resolved
$-select-height: map-get($sage-field-configs, height);
$-select-padding-x: map-get($sage-field-configs, padding);
$-select-padding-label: map-get($sage-field-configs, padding-label);
Expand Down
1 change: 1 addition & 0 deletions packages/sage-assets/lib/stylesheets/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
@import "components/data_card";
@import "components/description";
@import "components/dropdown";
@import "components/dynamic_select";
@import "components/empty_state";
@import "components/expandable_card";
@import "components/feature_toggle";
Expand Down
115 changes: 115 additions & 0 deletions packages/sage-system/lib/dynamicSelect.js
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,
}
})();
1 change: 1 addition & 0 deletions packages/sage-system/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ require('./hero')
require('./panel-controls')
require('./popover')
require('./search')
require('./dynamicSelect')

require('./init')
1 change: 1 addition & 0 deletions packages/sage-system/lib/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Sage.init = function(elementNamesToInitLegacy) {
initDocumentPresenceListener('[data-js-modaltrigger]', Sage.modal.initTrigger, false);
initDocumentPresenceListener('[data-js-tooltip]', Sage.tooltip.init, Sage.tooltip.unbind);
initDocumentPresenceListener('[data-js-dropdown]', Sage.dropdown.init, Sage.dropdown.unbind);
initDocumentPresenceListener('[data-js-dynamic-select]', Sage.dynamicSelect.init, Sage.dynamicSelect.unbind);
initDocumentPresenceListener('[data-js-sortable]', Sage.sortable.init, Sage.sortable.unbind);
initDocumentPresenceListener('[data-js-tabs]', Sage.tabs.init, Sage.tabs.unbind);
initDocumentPresenceListener('[data-js-copy-button]', Sage.copyButton.init, Sage.copyButton.unbind);
Expand Down