Erabi

Erabi — Pickers for Phoenix

Erabi (選び, "choosing") is pickers for Phoenix LiveView: the counterpart to Koyomi for everything that is not a date. It gives your app:

The pickers are a prebuilt JavaScript bundle in the Hex package, 14 KB gzipped; the color picker (6 KB) and the emoji and icon pickers (7 KB, and their emoji as the picker first opens) are entries of their own, for the apps that have them. Your app does not run Node.js for it: you add the hook and one style sheet.

What the pickers do

Pickers select/1 (typeahead; a box to filter from 10 options), multi_select/1 (chips; the list stays open), combobox/1 (type to find; create keeps free text), tags/1 (type, Enter or a comma; "Create “…”"), person_picker/1 (avatars, agents, me, multiple), color_picker/1 (swatches; custom for any color as #rrggbb), emoji_picker/1 (search, categories, tones, recent; your icons in a tab), icon_picker/1
Options anything a <select> takes; maps or Erabi.Option with :label, :hint, :color, :icon, :avatar, :kind, :group, :keywords, :priority, :disabled (or its reason); user structs by :id and :name
Forms posts name (or name[], after an empty one, so that none clears it), in the order chosen; phx-change and phx-submit as they are; errors with aria-invalid and aria-describedby; a <label for> names the picker
Rules required, min, max, create, min_length, max_length, pattern: in the browser and in Erabi.Changeset.validate_choice/4, with Ecto's messages and translate_error
Search a function of the query in the LiveView (Erabi.Live): pages of 50 as the list scrolls, async in a task, stale answers dropped, recent answers cached, a retry when one does not come
Matching case, accents and width do not count; letters in order ("nwiss" finds "New issue"); word starts first; one typo in a word of four letters; keywords and hints too
Looks chips in nine colors, avatars, icons drawn by your :icon slot, groups with headings, hints; light and dark; the Sumi theme
Accessibility the WAI-ARIA combobox (select-only and editable) and listbox patterns, aria-activedescendant, typeahead, announcements, forced colors, reduced motion
Words English, Portuguese, Spanish, French, German, Italian, Dutch and Japanese; yours with messages or Gettext
Choosing together with Issho: the others who have a picker open on its field, their options marked, a peek of the list where they are; Erabi.Issho.choose/5 for an agent to choose as people watch
Grids Erabi.cell_editor/2 and erabiEditors(): a picker as the editor of a Masu column
Testing Erabi.Test.choose/3, choices/2, chosen/2, search/3

Install

Erabi needs Elixir 1.18 or later (it uses the built-in JSON module), Phoenix 1.7.14 or 1.8, Phoenix LiveView 1.1 or later, and Ecto 3.13 or later.

Add erabi to the deps in mix.exs:

def deps do
  [
    {:erabi, "~> 0.1"}
  ]
end

Then run:

mix deps.get
mix erabi.install

The installer adds the hook to assets/js/app.js and the style sheet to assets/css/app.css, and prints what it changed. Give --dry-run to see the changes first. It does not add what a file already has, so you can run it again.

The manual steps

To make the same changes by hand, import the hook in assets/js/app.js and add it to the hooks of the LiveSocket:

import { Erabi } from "erabi"

const liveSocket = new LiveSocket("/live", Socket, {
  params: {_csrf_token: csrfToken},
  hooks: {...colocatedHooks, Erabi},
})

If you use color_picker, emoji_picker or icon_picker (or such a column in Masu), import their entries too. They are bundles of their own (6 and 7 KB gzipped), so that apps without them do not load them; without the import, such a picker stays the browser's <select>, and the console says what is missing:

import "erabi/color"
import "erabi/emoji"

The emoji picker fetches its emoji (a file of the page's language) from your endpoint; the installer adds the line that serves them, above the endpoint's own Plug.Static:

plug Plug.Static, at: "/erabi/emoji", from: {:erabi, "priv/emoji"}, gzip: true

Import the style sheet in assets/css/app.css (and the Sumi theme, if you want it: Sumi is the design system of Hattori AI, the makers of Erabi, Koyomi, Masu, Kotoba and Jumon). Put these lines below the other @import lines of app.css:

@import "../../deps/erabi/priv/static/erabi.css";
/* @import "../../deps/erabi/priv/static/erabi-sumi.css"; */

NODE_PATH=deps

import { Erabi } from "erabi" resolves through the NODE_PATH of your esbuild profile, which must have deps/. The Phoenix generators already set it. If your profile has no NODE_PATH, add it in config/config.exs:

env: %{"NODE_PATH" => [Path.expand("../deps", __DIR__), Mix.Project.build_path()]}

Use

Import the components in the html_helpers of your web module:

import Erabi.Components

Then, in a form:

<.select field={@form[:status]} options={@statuses} />
<.multi_select field={@form[:labels]} options={@labels} max={3} />
<.tags field={@form[:tags]} options={@tags} create />
<.person_picker field={@form[:owner_id]} people={@members} me={@current_user} />
<.color_picker field={@form[:color]} custom />
<.emoji_picker field={@form[:icon]} />

with options as the app has them:

@statuses [
  %{value: :todo, label: "To do", color: :gray},
  %{value: :doing, label: "In progress", color: :yellow},
  %{value: :done, label: "Done", color: :green}
]

and the same rules in the changeset:

def changeset(task, params) do
  task
  |> cast(params, [:status, :labels])
  |> Erabi.Changeset.validate_choice(:status, [required: true], options: @statuses)
  |> Erabi.Changeset.validate_choice(:labels, [max: 3], options: Labels.all())
end

A search on the server, as people type:

on_mount Erabi.Live
<.combobox field={@form[:city_id]} search={&Places.cities/1} selected={@city} />

Guides

Development

mix deps.get
mix erabi.build   # the bundle and the style sheets, into priv/static
mix dev           # every picker at http://localhost:4099

Tests

mix test                   # Elixir
mix test --include build   # the build and the package
mix test.js                # the browser's matcher and rules
mix test.e2e               # the pickers in Chromium, Firefox and WebKit

The rules and the matcher that both the server and the browser apply are tested on both sides with one fixture each: test/fixtures/rules.json, and test/fixtures/match.json, which is Jumon's.

Release

mix release builds the bundle, checks priv/static and the version of package.json, publishes to Hex and tags the version.

Contributing

See CONTRIBUTING.md: when a change needs an issue first, and what a pull request needs.

License

Erabi is released under the MIT License. See LICENSE. Its icons are Lucide's (ISC). See NOTICE.