Packages

Mishka Chelekom composites for Mob apps — plugin-shipped, no vendoring into user code

Current section

Files

Jump to
mob_mishka README.md
Raw

README.md

# mob_mishka

Mishka Chelekom composites for [Mob](https://github.com/GenericJam/mob) apps, shipped as a proper Hex plugin instead of vendored into every generated app.

Seventy-three composites — dialogs, tabs, sliders, hue/alpha pickers, semi-circle progress, angle slider, and the rest — that used to live as `.ex` copies inside every `mix mob.new` project now ship from one place, upgrade like any other dep, and stay swappable for users who want to edit their own copy.

## Why this exists

Before this plugin, `mob_new` baked 75+ Mishka composite files into every generated app's `lib/<app>/components/` at generation time. Regenerating an app pulled a fresh snapshot; any user edits to a composite diverged from upstream forever. There was a bespoke `mix mob_new.sync_mishka` task coupling the mob_new archive to Mishka's source, and a `mishka_chelekom` mix task that used to write into `deps/mob/priv/tags/*.txt` to silence sigil warnings.

This plugin replaces all three:

- **Composites ship as a Hex dep**, callable directly and expandable via `~MOB` sigil tags. No files copied into your project by default.
- **Sigil whitelist membership rides the plugin manifest** — see [MOB-247](https://github.com/GenericJam/mob/commit/8bc6c62) in mob. No `config :mob, :extra_tags` block needed for tags this plugin ships.
- **`mix mob_mishka.gen <name>`** opts a specific composite into "copy source into my `lib/` so I can edit it," preserving Mishka's design-system value prop for users who want it — just no longer the default.

## Install

Requires mob 0.9.0 or newer (plugin-manifest tag discovery is what lets the sigil compile `<MishkaHueSlider>` and its siblings without an `extra_tags` block).

Add to your Mob app's `mix.exs`:

```elixir
def deps do
  [
    {:mob,         "~> 0.9.0"},
    {:mob_mishka,  "~> 0.1"}
  ]
end
```

Then in `mob.exs`, activate the plugin:

```elixir
config :mob, :plugins, [:mob_mishka]
```

That is the entire setup. No `Components.register_all/0` call in your `on_start/0`, no `extra_tags` config, no `deps/mob/priv/tags/*.txt` edits — `MobMishka.register_all/0` runs from the plugin's manifest `:lifecycle.on_start` when your app boots.

## What's in it

Seventy-three composites plus three support modules (`Anchored`, `Color`, `Event`). A few of the more useful ones:

| Composite | What it is |
|---|---|
| `<MishkaDialog>` | Modal dialog with confirm/dismiss buttons |
| `<MishkaTabs>` | Tab bar with selectable panels |
| `<MishkaAccordion>` | Expand/collapse panels with open-change events |
| `<MishkaHueSlider>` / `<MishkaAlphaSlider>` | HSL/HSV colour pickers on a canvas |
| `<MishkaSlider>` | Range slider with snap support |
| `<MishkaAngleSlider>` | Circular dial for a 0–360° angle |
| `<MishkaSemiCircleProgress>` | Half-circle gauge |
| `<MishkaLoadingOverlay>` | Full-screen loading indicator |
| `<MishkaSeparator>` / `<MishkaSpoiler>` / `<MishkaVisuallyHidden>` | Layout helpers |
| `<MishkaJsonInput>` | Validating JSON text editor |

Use any of them straight from a screen:

```elixir
def render(assigns) do
  ~MOB"""
  <Column padding={16}>
    <MishkaSemiCircleProgress value={@battery} label="Battery" />
    <MishkaAngleSlider value={@angle} on_change={:angle} />
  </Column>
  """
end

def handle_info({:tap, :angle, deg}, socket) do
  {:noreply, Mob.Socket.assign(socket, :angle, deg)}
end
```

The full list of registered tags lives in [`priv/mob_plugin.exs`](priv/mob_plugin.exs) and is the same list `Mob.Composite.expanders/0` returns at runtime.

## Migrating an existing app

If your app was generated by `mob_new` before the vendored composites were dropped (mob_new < 0.6), see [MIGRATIONS.md](MIGRATIONS.md). Short version: add the dep, run `mix mob_mishka.migrate` to preview which vendored composite files can be deleted, then `mix mob_mishka.migrate --apply` to make the changes. User-edited copies are preserved as overrides.

Also strips the compat `config :mob, :extra_tags` block from `config/config.exs` when combined with `--remove-extra-tags`, once your `mob` dep supports plugin-manifest tag discovery (0.9.0+).

## Ejecting a composite for editing

```
mix mob_mishka.gen dialog          # or: mix mob_mishka.gen mishka_dialog
mix mob_mishka.gen --all           # eject every composite the plugin ships
```

Copies `MobMishka.Components.MishkaDialog` into `lib/<your_app>/components/mishka_dialog.ex` as `<YourApp>.Components.MishkaDialog`. Activate the ejected copies once (`config/config.exs`):

```elixir
config :mob_mishka, :override_namespace, YourApp.Components
```

`MobMishka.register_all/0` sees the app-local module at boot and prefers it over the plugin's default, so editing your copy takes effect on the next compile with no re-registration. Delete the file to fall back to the plugin's version.

Sibling aliases inside the ejected file still point at the plugin (e.g. `MishkaCloseButton` ejected on its own still calls the plugin's `MishkaActionIcon`). If you also want to edit a sibling, eject it too and fix up the alias by hand.

## Design credit

The composites, colour theory, and visual language are Mishka Chelekom's work — see [mishka-group/mishka_chelekom](https://github.com/mishka-group/mishka_chelekom). This plugin is the mob-side surface: the Mob composites drawn from the same design intent, plus the plugin-manifest wiring that makes them ship without vendoring.

The Phoenix/LiveView side of Chelekom is untouched by this — only the mob composites moved. Existing `mishka_chelekom` users see one deprecation cycle on the mob mix tasks that redirect to `mob_mishka`.

## Related

- [MOB-246 epic](https://linear.app/mobframework/issue/MOB-246) — full context for the extraction, plus seven child issues covering plugin-manifest discovery, this scaffold, the composite port, the mix-task move, opt-in vendoring, mob_new template surgery, and the migration guide.

## License

MIT.