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 all 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
9 changes: 9 additions & 0 deletions docs/app/helpers/components_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,15 @@ def sage_components
react: "todo",
a11y: "done"
},
{
title: "dynamic_select",
description: "A description is about the dynamic select.",
scss: "done",
docs: "todo",
rails: "done",
react: "todo",
a11y: "todo"
},
{
title: "form_input",
description: "Basic text input form fields with 'floating' labels",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<h3>Working with the<code>Sage Dynamic Select Input (Select2)</code></h3>
<p>
The dynamic select input is a sagified version of the select2 input that resides in <code>Kajabi Products.</code> The input fuctionality leverages the Javascript library select2 and includes several options for tailoring the input to the UI. The input can retrieve options from any arbitrary API that will return validly structured JSON.<br/> <br/> Basic usage and complete documentation ca be found
Copy link
Member

Choose a reason for hiding this comment

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

😍 Beautifully written!

One suggestion, we can replace the <br> tags with a new <p>

Copy link
Contributor

Choose a reason for hiding this comment

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

Note as well that the md() helper may be useful to simplify as well.

<a href="https://select2.org/getting-started/basic-usage" target="_blank"><code>here</code></a>.
</p>

<h4>Error Handling</h4>
<p>Much like other sage inputs, the dynamic select includes options to render error messages from the server. By using the <code>has_error</code> attribute, one can trigger the erros states and print an error message that gets placed below the input.</p>
Copy link
Member

Choose a reason for hiding this comment

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

Tiny typo here "error states"


<pre class="prettyprint">
<code>
// Component API
name: "Input Form Name",
label: "Input Label",
id: "Input id",
required: true
has_error: false
message: "Error message goes here"
url: "Endpoint for API",
default_value: "Prefill select input with default value",
default_text: "Prefill select input with default title",
theme: "Choose Sage or Bootstrap for legacy input",
search: "Allow user to search through retrieved results",
clear: "Allow user to clear input value"

// Example Implementation
sage_component SageDynamicSelect, {
Copy link
Member

Choose a reason for hiding this comment

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

As @philschanely had mentioned earlier, ideally we'd have a rendered preview of the component in action, but this works as a substitute.

As a follow-up, I wonder if we could reference the external JS dependencies (jquery and select2) from a CDN just for this preview page. That'd give us a working preview but prevent the dependencies from being packaged up.

Copy link
Contributor

Choose a reason for hiding this comment

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

We may be able to add them as peer dependencies in the repo. @voodooGQ would this make sense?

name: "site[home_landing_page_id]",
label: "Landing Page",
id: "site_home_landing_page_id",
url: admin_site_select_options_path(site, :landing_page, { published: true }, format: :json),
default_value: site.home_landing_page.id,
default_text: site.home_landing_page.title,
theme: "sage",
search: true,
clear: false
}

</code>
</pre>


84 changes: 84 additions & 0 deletions docs/app/views/examples/components/dynamic_select/_props.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<tr>
<td><%= md('`url`') %></td>
<td><%= md('API URL for retrieving select options') %></td>
<td><%= md('String') %></td>
<td><%= md('`nil`') %></td>
</tr>
<tr>
<td><%= md('`label`') %></td>
<td><%= md('Label for the input') %></td>
<td><%= md('String') %></td>
<td><%= md('`component.id`') %></td>
</tr>
<tr>
<td><%= md('`name`') %></td>
<td><%= md('Input name') %></td>
<td><%= md('String') %></td>
<td><%= md('`nil`') %></td>
</tr>
<tr>
<td><%= md('`id`') %></td>
<td><%= md('Input ID') %></td>
<td><%= md('String') %></td>
<td><%= md('`nil`') %></td>
</tr>
<tr>
<td><%= md('`has_error`') %></td>
<td><%= md('Enabling this property adds the `.sage-input--error` class to the component.') %></td>
<td><%= md('Boolean') %></td>
<td><%= md('`nil`') %></td>
</tr>
<tr>
<td><%= md('`message`') %></td>
<td><%= md('Sets the message text for the component.') %></td>
<td><%= md('String') %></td>
<td><%= md('`nil`') %></td>
</tr>
<tr>
<td><%= md('`placeholder`') %></td>
<td><%= md('Displays placeholder text when the input is empty.') %></td>
<td><%= md('String') %></td>
<td><%= md('`component.label || component.id`') %></td>
</tr>
<tr>
<td><%= md('`default_value`') %></td>
<td><%= md('Default value for the input') %></td>
<td><%= md('String') %></td>
<td><%= md('`nil`') %></td>
</tr>
<tr>
<td><%= md('`default_text`') %></td>
<td><%= md('Default text for the input') %></td>
<td><%= md('String') %></td>
<td><%= md('`nil`') %></td>
</tr>
<tr>
<td><%= md('`paginate`') %></td>
<td><%= md('Allows input to paginate results per 25 items') %></td>
<td><%= md('Boolean') %></td>
<td><%= md('`nil`') %></td>
</tr>
<tr>
<td><%= md('`search`') %></td>
<td><%= md('Displays a search bar for users to search through results') %></td>
<td><%= md('Boolean') %></td>
<td><%= md('`false`') %></td>
</tr>
<tr>
<td><%= md('`clear`') %></td>
<td><%= md('Displays an "x" button for users to clear the current value.') %></td>
<td><%= md('Boolean') %></td>
<td><%= md('`false`') %></td>
</tr>
<tr>
<td><%= md('`required`') %></td>
<td><%= md('Makes input selection required') %></td>
<td><%= md('Boolean') %></td>
<td><%= md('`nil`') %></td>
</tr>
<tr>
<td><%= md('`theme`') %></td>
<td><%= md('Choose from legacy UI or Sage') %></td>
<td><%= md('string') %></td>
<td><%= md('`sage`') %></td>
</tr>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

19 changes: 19 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,19 @@
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],
paginate_size: [:optional, Integer],
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,44 @@

<div class="
sage-dynamic-select
<%= "sage-select--error" if component.has_error %>
<%= component.generated_css_classes %>">
<% unless component.theme.present? && 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-placeholder="<%= component.placeholder || component.label || component.id %>"
<% if component.default_value.present? %>
data-default-value="<%= component.default_value %>"
<% end %>
<% if component.default_value.present? %>
data-default-text="<%= component.default_text %>"
<% end %>
<% if component.paginate.present? %>
data-paginate="<%= component.paginate %>"
data-paginate-size="<%= component.paginate_size || 25 %>"
<% end %>
<% if component.search.present? %>
data-search="<%= component.search %>"
<% end %>
<% if component.clear.present? %>
data-clear="<%= component.clear %>"
<% end %>
<% if component.theme.present? %>
data-theme="<%= component.theme %>"
<% else %>
data-theme="sage" %>
<% end %>
<%= '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,97 @@
$-dynamic-select-default-height: rem(45);
$-dynamic-select-default-padding: rem(4) sage-spacing(sm) 0;
$-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(54);
Copy link
Member

Choose a reason for hiding this comment

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

We don't have an existing spec in Figma for this, so I won't consider it a blocker but do want to note that the height for the component is much taller (3.375rem) than the standard SageFormSelect field (2.25rem) with an option selected. This shouldn't be an issue for standalone fields, but in cases where inputs are horizontally aligned, the difference will be noticeable:

SageFormSelect SageDynamicSelect
selected-single selected-select2

$-dynamic-select-selected-padding: rem(12) 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);

/* stylelint-disable selector-max-compound-selectors */

.sage-dynamic-select .sage-dynamic-select__data {
bottom: sage-spacing(xs);
left: 50%;
}

// empty state //
.sage-dynamic-select {
position: relative;
.select2-container--sage .sage-select__arrow::before {
top: rem(3);
}

.select2-container--sage .select2-dropdown--below {
margin-top: sage-spacing(xs);
}

.select2-container--sage .select2-selection--single.sage-select__field {
height: $-dynamic-select-default-height;
padding: $-dynamic-select-default-padding;
}

.select2-container--sage .select2-selection--single.sage-select__field .select2-selection__placeholder {
color: $-dynamic-select-placeholder-color;
}
.select2-container--sage .select2-selection--single.sage-select__field .select2-selection__clear {
float: right;
}
.select2-container--sage .select2-selection--single.sage-select__field .select2-selection__arrow b {
display: none;
}

// open state //
.select2-container--sage.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--sage.select2-container--open .sage-select__arrow::before {
transform: $-dynamic-select-open-arrow;
}

.select2-container--sage.select2-container--open .sage-select__arrow::before,
.select2-container--sage:hover .sage-select__arrow::before {
color: $-dynamic-select-color-success;
}

// focused state //
.select2-container--sage.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--single.sage-select__field .select2-selection__rendered {
@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--single.sage-select__field .select2-selection__rendered .select2-selection__clear {
font-weight: $-dynamic-select-clear-font-weight;
}
}

/* stylelint-enable selector-max-compound-selectors */
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