-
Notifications
You must be signed in to change notification settings - Fork 41
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
New variant for tags-mode #78
Conversation
691c311
to
f4d4e24
Compare
Hi @ringvold thanks a lot for this, I just took a superficial look but it sounds like solid work 💪 using checkboxes in the dropdown is something I've been thinking of adding for a long time now. Unfortunately, I'm quite busy this coming week, but I will take a proper look as soon as I find some time. Cheers! |
Hi @ringvold I finally found the time to read carefully what you wrote (I've been really busy in the past few weeks, still busy but things are getting a bit better). I haven't looked at the code yet. Here's a couple of comments:
EDIT: thinking about it a bit more, a simple additional boolean attribute
<:option :let={%{label: label, value: value, selected: selected}}>
In this way, old code that ignores the selected field would still work
Let me know what you think |
Hi! Thought I'd get back to you sooner but stuff happened here as well.
Functionally it is a bit more than "don't close the dropdown" (as I made it here at least) as it also enables you to deselect from the drop down which I believe is not possible on "normal" tags mode. I think that might be why I think of it more as a variant of tag mode. You should have the final say on the API but I feel like So I lean towards
Other things of noteI have noticed that this PR breaks the current behavior for tags as you can deselect from the drop down. Which you can not to in the current released version. I have tried to not change the current functionality of tag mode so I'm not sure what happened there. I consider that a bug. One thing I have not figured out yet (although not look at it very much yet) is how to not loose focus of the element when selecting it when operating it with keyboard only. Currently it resets focus to the search box when selecting. I would like to keep it in the dropdown at/around the selected element to be able to continue the selection. Any thoughts on this? |
Yeah apologies I hadn't realized you can deselect from the dropdown...
And this is exactly what I meant by "using checkboxes to deselect". Ok so I agree with you now, close_dropdown_on_select obviously doesn't cut it. A third selection mode is more appropriate. quick_tags sounds good to me!
You mean custom fields they're passing to the slots? To preempt this, we could simply taise an exception when someone uses the selected key for the options. I will take a look at the last 2 points you mentioned (breaking current behavior and not losing focus) soon. But it sounds like at least we agreed on how to expose this new mode in the API and how to pass the selected status in the slots :) |
Yes, it does sound like we are onto something her 😄
That is maybe also a breaking change but certainly much smaller than my suggestion. I will make a suggestion on the default styles for this mode. I cant remember the details but there are some changes that need to be made to make it look decent out of the box. |
I don't think this is a breaking change because we're not breaking any API. The docs say that the options can be specified as maps with the shape |
Is this one good to merge? |
@egeersoz I still have to implement what me and Max talked about here, add test and then probably a new check by @maxmarcon. I hope to find some time to look at it this weekend though. :) |
Life happens and I have not gotten to this when I thought. 🕙🏃➡️ @maxmarcon Looking a bit at it now and I remember why I did this functionality as a new variant for tags. It is a bit easier to implement when it is just a variant of tags as we can piggyback on the existing tags functionality with some other minor changes. Introducing a new mode we have to make sure not to mess up On another note my fix for the dropdown removal on first select did not work. It basically just removed all The fix seems to be to always render the tags container div but conditionally render the rest. (It might be the re rendering when the first tag is added that triggers the blur event that hides the dropdown.) In practice it would be to move the Hoping to have something to look at soon. |
40e36ff
to
666399a
Compare
The multiple select option for the new tags_mode makes it possible to select multiple options at once without needing to search or force the dropdown to appear. It is a kind of hybrid of the search variant and a normal multi select input input.
Do not move focus back to search box on first select. Also refactor to use more efficient way of checking for already selected.
Adds toggle to showcase app to view example of custom html for options list. Also fixes issue with dropdown disappearing when selecting first time in quick_tags mode.
4ad12d5
to
4717fbf
Compare
Rebased on latest master. Fixed tests for tags and added tests for quick_tags. |
4717fbf
to
3885edb
Compare
3885edb
to
995b45c
Compare
@maxmarcon Let me know if there are any more things that need to be fixed og changed. :) When I think about it we might need to add some documentation. 🤔 |
@maxmarcon Can this be merged? |
@ringvold this looks awesome, thank you! Review coming soon. FYI: I pushed a couple of minor commits to your branch just to test if I could do it (I updated a line in the docs). |
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.
When I think about it we might need to add some documentation. 🤔
Don't worry, I will add it after this MR is merged. It's not going to be a lot of documentation anyway
lib/live_select/component.ex
Outdated
defp next_selectable(%{ | ||
options: options, | ||
active_option: active_option, | ||
mode: :quick_tags | ||
}) do | ||
options | ||
|> Enum.with_index() | ||
|> Enum.reject(fn {opt, _} -> active_option == opt end) | ||
|> Enum.map(fn {_, idx} -> idx end) | ||
|> Enum.find(active_option, &(&1 > active_option)) | ||
end |
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.
this is unnecessary code duplication IMO. All you need to do is change like 717 to something like:
|> Enum.reject(fn {opt, _} -> active_option == opt || (mode != :quick_tags && already_selected?(opt, selection)) end)
and you have a version that works for both :quick_tags
and non-:quick_tags
modes :)
lib/live_select/component.ex
Outdated
defp prev_selectable(%{ | ||
options: options, | ||
active_option: active_option, | ||
mode: :quick_tags | ||
}) do | ||
options | ||
|> Enum.with_index() | ||
|> Enum.reverse() | ||
|> Enum.reject(fn {opt, _} -> active_option == opt end) | ||
|> Enum.map(fn {_, idx} -> idx end) | ||
|> Enum.find(active_option, &(&1 < active_option || active_option == -1)) | ||
end | ||
|
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.
this is unnecessary code duplication IMO. All you need to do is change like 746 to something like:
|> Enum.reject(fn {opt, _} -> active_option == opt || (mode != :quick_tags && already_selected?(opt, selection)) end)
and you have a version that works for both :quick_tags
and non-:quick_tags
modes :)
lib/live_select/component.html.heex
Outdated
class(@style, :tags_container, @tags_container_class, @tags_container_extra_class) | ||
}> | ||
<div class={class(@style, :tags_container, @tags_container_class, @tags_container_extra_class)}> | ||
<%= if (@mode == :tags || @mode == :quick_tags) && Enum.any?(@selection) do %> |
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.
I prefer @mode in [:tags, :quick_tags]
, I think it's more readable (nitpick, I know)
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.
Agree!
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.
@ringvold, thanks again for the great work. The feature looks amazing and you also added the styling of the options as checkboxes as a bonus. Appreciated that you also changed the showcase app, thanks a ton!
I'm requesting a few changes (mostly minor code refactoring, and one change that will simplify how the default styles for quick_tags are set in the showcase app)
I pushed a modification to a test in your branch so that even the upwards navigation in the new quick_tags
mode is covered...
Again, this is great stuff!!! Very eager to merge this one
@@ -67,9 +67,15 @@ defmodule LiveSelectWeb.ShowcaseLive do | |||
field(:allow_clear, :boolean) | |||
field(:debounce, :integer, default: Component.default_opts()[:debounce]) | |||
field(:disabled, :boolean) | |||
field(:custom_option_html, :boolean) |
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.
I love that you went out of the way to create this toggle to show how to style the options as checkboxes!
Can we please rename it to something more specific like "Options styles as checkboxes" ?
@@ -71,6 +71,10 @@ | |||
<span class="label-text mr-1">Disabled: </span> | |||
{checkbox(@settings_form, :disabled, class: "toggle")} | |||
<% end %> | |||
<%= label class: "label cursor-pointer" do %> | |||
<span class="label-text mr-1">Custom option HTML: </span> | |||
<%= checkbox(@settings_form, :custom_option_html, class: "toggle") %> |
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.
I love that you went out of the way to create this toggle to show how to style the options as checkboxes!
Can we please rename it to something more specific like "Options styles as checkboxes" ?
@@ -326,7 +345,10 @@ | |||
<.copy_to_clipboard_icon /> | |||
</button> | |||
</div> | |||
<Render.live_select opts={Settings.live_select_opts(@settings_form.data, true)} /> | |||
<Render.live_select | |||
custom_option_html={@settings_form[:custom_option_html].value} |
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.
the argument of the function component should also be renamed to options_styled_as_checkboxes
attrs = | ||
if settings.mode == :quick_select do | ||
%{selected_option_class: "cursor-pointer font-bold hover:bg-gray-400 rounded"} | ||
else | ||
%{} | ||
end | ||
|
||
socket = | ||
socket | ||
|> assign(:settings_form, Settings.changeset(settings, %{}) |> to_form) | ||
|> assign(:settings_form, Settings.changeset(settings, attrs) |> to_form) |
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.
Suggestion: let's remove this code and all other changes in this file related to the selected_option_class
override, and add this:
params =
if params["mode"] == "quick_tags" do
Map.put_new(
params,
"selected_option_class",
"cursor-pointer font-bold hover:bg-gray-400 rounded"
)
else
params
end
just before we create the changeset. Advantages:
- simpler
- more transparent, as the user will see the classes in the UI's styling options
- the user can override these default classes if they want to
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.
Yeah, what I had done was kind of a hack to just to make it work.
This is nice, but it seems to not reset when you change back to tags mode.
Maybe do a reset of selected_option_class
in the else clause or just let it be?
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.
I think just let it be, at least for now. I mean if the user changes the style while in quick_tags mode you don't want it to be reset when you change back to tags mode... the user will lose what they entered.
I guess for now this is the best we can do, we can come up with a better way later :)
42fc146
to
fb80e77
Compare
fb80e77
to
f9607d0
Compare
I think all comments should have been addressed now. Let me know if I missed something. :) |
🥳 🚀 💪 |
Hi! 😄
This is a suggestion for a new variant of the tag mode. The multiple_select option for the new tags_mode attribute makes it possible to select multiple options quickly without needing to search or force the dropdown to appear. The current tag mode hides the dropdown-part when a option is selected. This approach could probably be described as a kind hybrid of the normal live_select tag mode and a normal multi select input.
A new attribute
tags_mode
is added with:default
(how tags work today) and:multiple_select
as possible options.Example usage taken from demo-app:
There is some code in the demo app related to selected_option_class as this feature would need some new style defaults/style changes to look passable out of the box. This should probably be solved some other way. Maybe by adding some new default styles.
The name chosen, multiple_select, is very much up for debate. I just needed something to work with.
Potential performance issue
When testing in the demo app I do notice some slowness when selecting or deselecting many options in rapid succession. I did not experience this in our app. I am not sure if this is a flaw in the implementation or just that the dataset for the demo app is very large. Any suggestions on this point is especially welcome.
Breaking changes
The feature also changes the value exposed to the option-slot to also return an indication if the option is already selected. This is one of the main points of the change as the impetus for this feature is a design/UX that displays the selected with a checkbox. See attached picture for example. I suppose this could be done with just styles but the design seems harder to replicate that way and I also think it would be nice to be able to have that info available to the slot. But still open for debate. :)
Example of option-slot usage:
Notice the :let now exposing a tuple which contains the option and a selected boolean.
Rendered example:
This is a suggestion based on functionality that we where in need of and I hope it can be added to the project in some form.
This is not expected to be merged as-is (ex. tests is missing for now) but it is working code that we would use in our fork and a good point to discuss from I think.
I'm aware that this might not be something that this project wants and I'm happy to make changes or discuss the approach. Maybe there is a better way to achive the same or something very similar?
The changes necessary does not seem to invasive though from my perspective (except maybe the option-slot breaking change 😅).
Let me know what you think. 😄
EDIT: I forgot while writing this, but there is also a bug in this implementation I have not figured out. When making the first selection the blur event triggers and hides the dropdown. This does not happen on other selections but consistently happens from state of nothing selected to first option selected.