Components Combo-box
Petal Framework

Combo-box

v0.4.0 +
A dynamic, stylish and feature-rich combo-box as a HEEX component.
Or add your own!

How to access

Membership
$299
-> 
See plans

This comes with the Petal Framework private hex package. Purchase a membership to get access to this package. It can be used with any Phoenix project. Post-expiration, you'll retain access but won't be eligible for updates from newer versions.

Or
Single purchase
$49
-> 
Buy

Purchase the files for this component only. Useful if you just want this component but don't need the other membership benefits.

Installation

Import the CSS file in your app.css:

@import "tailwindcss/base";
@import "../../deps/petal_components/assets/default.css";
@import "../../deps/petal_framework/assets/css/combo-box.css"; /* <-- Add this line */
@import "tailwindcss/components";
@import "tailwindcss/utilities";

Properties

          attr :field, :any,
  doc:
    "the field to generate the input for. eg. `@form[:name]`. Needs to be a %Phoenix.HTML.FormField{}."

attr :class, :string, default: nil, doc: "the class to add to the input"
attr :wrapper_class, :string, default: nil, doc: "the wrapper div classes"

attr :options, :list,
  doc:
    ~s|A list of options. eg. ["Admin", "User"] (label and value will be the same) or if you want the value to be different from the label: ["Admin": "admin", "User": "user"]. We use https://hexdocs.pm/phoenix_html/Phoenix.HTML.Form.html#options_for_select/2 underneath.|,
  default: []

attr :multiple, :boolean, default: false, doc: "can multiple choices be selected?"
attr :create, :boolean, default: false, doc: "create new options on the fly?"
attr :help_text, :string, default: nil, doc: "context/help for your field"

attr :max_items, :integer, default: nil, doc: "The maximum number of items that can be selected"

attr :remote_options_event_name, :string,
  default: nil,
  doc:
    "The event name to trigger when searching for remote options. That event must return a li"

attr :remote_options_target, :string,
  default: nil,
  doc:
    ~s|the target of the call for remote options. Will default to the current live view. For a live component, pass `remove_options_target={@myself}` if the event is handled on the live component.|

attr :remove_button_title, :string,
  default: "Remove this item",
  doc: "The title for the remove item button"

attr :placeholder, :string, default: "Select an option...", doc: "The placeholder text"

attr :tom_select_plugins, :map,
  default: %{},
  doc:
    ~s|Which plugins should be activated? Pass a map that will be converted to a Javascript object via JSON. eg. `%{remove_button: %{title: "Remove!"}}`. See https://tom-select.js.org/plugins for available plugins.|

attr :tom_select_options, :map,
  default: %{},
  doc:
    "Options to pass to Tom Select. Uses camel case. eg `%{maxOptions: 1000}`. See https://tom-select.js.org/docs for options."

attr :id, :any,
  default: nil,
  doc: "the id of the input. If not passed, it will be generated automatically from the field"

attr :name, :any,
  doc: "the name of the input. If not passed, it will be generated automatically from the field"

attr :label, :string,
  doc:
    "the label for the input. If not passed, it will be generated automatically from the field"

attr :value, :any,
  doc:
    "the value of the input. If not passed, it will be generated automatically from the field"

attr :errors, :list,
  default: [],
  doc:
    "a list of errors to display. If not passed, it will be generated automatically from the field. Format is a list of strings."

attr :tom_select_options_global_variable, :string,
  default: nil,
  doc:
    ~s|for when you want to manually pass the options to Tom Select. eg. inside some script tags: `window.myOptions = { render: {...}}`. And in your component:`tom_select_options_global_variable="myOptions"`. It will merge the options with the existing ones.|

attr :rest, :global,
  include:
    ~w(autocomplete disabled form max maxlength min minlength list
  pattern placeholder readonly required size step value name multiple selected default year month day hour minute second builder options layout cols rows wrap checked accept),
  doc: "All other props go on the input"

        

Single select

Just like a normal select, but a user can search for the option.

          <.combo_box
  label="Pick your favourite fruit"
  options={["Apple", "Banana", "Orange", "Pineapple", "Strawberry"]}
  field={@form[:fruit]}
/>

        

Multiple select

The input can take multiple options. You can use backspace/delete to delete options.

          <.combo_box
  multiple
  label="Pick your favourite fruit"
  options={["Apple", "Banana", "Orange", "Pineapple", "Strawberry"]}
  field={@form[:fruit]}
/>

        

If you want to use your live view as a remote data source, you can set the remote_options_event_name option, which is similar to a phx-change event. When a user starts typing this will trigger an event with the name you pass. You handle the event in your live view return a list of options. The event will be passed the search term as first argument.

Remote options search with a live component

By default the eventcombo_box_search is handled on the live view. But sometimes you may have your Combo Box in a live component and want that live component to contain the event handler. In this case you can tell the Combo Box to target the current live component using the @myself assigns:

<.combo_box
  remote_options_event_name="live_component_combo_box_search"
  remote_options_target={@myself}
  label="Country"
  placeholder="Search for a country..."
  field={@form[:country]}
/>

The key addition is remote_options_target={@myself}. This value essentially gets passed along to the Javascript function pushEventTo(selectorOrTarget, event, payload, (reply, ref) => ...) as the selectorOrTarget param. See docs.

Create new option

The user can create a brand new option if they like.

          <.combo_box
  create
  label="Pick your favourite fruit"
  options={["Apple", "Banana", "Orange", "Pineapple", "Strawberry"]}
  field={@form[:fruit]}
/>

        

Disabled

Disabled works like any other field.

          <.combo_box
  disabled
  label="Pick your favourite fruit"
  options={["Apple", "Banana", "Orange", "Pineapple", "Strawberry"]}
  field={@form[:fruit]}
/>

        

Max items

Set the max number of items that can be selected.

          <.combo_box
  label="Pick your favourite fruit"
  max_items={2}
  options={["Apple", "Banana", "Orange", "Pineapple", "Strawberry"]}
  field={@form[:fruit]}
/>

        

Placeholder

Placeholder text similar to a normal text input.

          <.combo_box
  placeholder="AI will take our jobs :("
  label="Pick your favourite fruit"
  options={["Apple", "Banana", "Orange", "Pineapple", "Strawberry"]}
  field={@form[:fruit]}
/>

        

Option groups

          <.combo_box
  options={[Birds: ["Eagle", "Seagull"], Animals: ["Dog", "Rhino"]]}
  label="Pick your favourite fruit"
  field={@form[:fruit]}
/>

        

Plugins

You can toggle plugins and pass options to them.

See https://tom-select.js.org/plugins for available plugins. We have enabled two by default: “Remove button” and “Checkbox options” for multiple selects. You can disable them though:

Disable remove button

          <.combo_box
  tom_select_plugins={%{remove_button: false}}
  multiple
  label="Pick your favourite fruit"
  options={["Apple", "Banana", "Orange", "Pineapple", "Strawberry"]}
  field={@form[:fruit]}
/>

        

Drag and drop

Note that this requires jQuery and jQueryUI. We've made it so these libraries will automatically be fetched from a CDN if the `drag_drop` plugin is enabled.

          <.combo_box
  tom_select_plugins={%{drag_drop: true}}
  label="Pick your favourite fruit"
  options={["Apple", "Banana", "Orange", "Pineapple", "Strawberry"]}
  field={@form[:fruit]}
/>

        

Custom Tom Select options

Simple options

We have two ways of providing Tom Select options. You can pass in a map to the attribute tom_select_options, which works for options of primitive types such as strings or numbers.

<.combo_box
  tom_select_options={%{highlight: false}}
  label="Pick your favourite fruit"
  options={["Apple", "Banana", "Orange", "Pineapple", "Strawberry"]}
  field={@form[:fruit]}
/>

Complex options

For complex options that require setting a Javascript function as an option, we have made it so you can reference a global Javascript variable that contains those options:

<script phx-update="ignore" id="customTomSelectOptions">
  window.customTomSelectOptions = {
    render: {
      option_create: function(data, escape) {
        return '<div class="create">Create new option for <strong>' + escape(data.input) + '</strong>&hellip;</div>';
      },
      no_results:function(data,escape){
        return '<div class="no-results">No results found for "'+escape(data.input)+'"</div>';
      },
    }
  }
</script>

<.h3 class="mt-10">Remote fetching</.h3>
<.combo_box
  label="Fetch remotely"
  id="fetch_remotely"
  tom_select_options_global_variable="customTomSelectOptions"
  multiple
  remote_options_event_name="combo_box_search"
  field={@form[:widget_category_names]}
  help_text="The options are fetched remotely in your live view"
/>

In the above code you can see we set a global Javascript variable called customTomSelectOptions and then pass it to our combo box via the attribute tom_select_options_global_variable="customTomSelectOptions".

Note that we merge these options with any other options you pass in, so you can combine the tom_select_options and tom_select_options_global_variable methods of passing options.

Customization

You can copy the CSS into your own project:

          cp deps/petal_framework/assets/css/combo-box.css ./assets/css

        

Then modify your `app.css` to point to it.

          @import "tailwindcss/base";
@import "../../deps/petal_components/assets/default.css";
@import "combo-box.css"; /* <-- Add this line */
@import "tailwindcss/components";
@import "tailwindcss/utilities";

        

Now you can customize `combo-box.css` to your needs.

Note that we also use styles from Petal Components for the label, help text and errors.