README.md

# Turboprop

[![Hex.pm](https://img.shields.io/hexpm/v/turboprop)](https://hex.pm/packages/turboprop)
[![npm](https://img.shields.io/npm/v/@leuchtturm/turboprop)](https://npmjs.com/package/@leuchtturm/turboprop)
[![Documentation](https://img.shields.io/badge/documentation-gray)](https://hexdocs.pm/turboprop/)

<img src="https://github.com/leuchtturm-dev/turboprop/raw/main/assets/turboprop.png" width="200" />

<!-- MDOC !-->

A toolkit to build beautiful, accessible components for Phoenix using Tailwind and Zag.

## Parts

Turboprop consists of multiple parts, each with their own purpose in building your component library.

### Turboprop Hooks

Turboprop Hooks allow adding a ton of accessibility features to your components by simply adding a hook and a few data attributes to them.  
This includes:

- Keyboard interactions
- Focus management
- ARIA attributes

You can either install and use them through the hex.pm dependency and some helpers we offer to add the relevant attributes to a component,
or install them directly through npm and adding the attributes yourself.  

As an example, this renders a fully accessible dropdown menu:

```heex
<div {menu()}>
  <button
    class="rounded-md bg-blue-500 px-3 py-1.5 text-sm text-white shadow-sm hover:bg-blue-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500"
    {menu_trigger()}
  >
    Menu
  </button>
  <div {menu_positioner()}>
    <div
      class="z-10 w-48 text-sm origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
      {menu_content()}
    >
      <Phoenix.Component.link navigate="/link" class="block px-4 py-2 outline-0 data-[highlighted]:bg-gray-100" {menu_item()}>
        Link
      </Phoenix.Component.link>
      <a href="/anchor" id="test" class="block px-4 py-2 outline-0 data-[highlighted]:bg-gray-100" {menu_item()}>Anchor</a>
    </div>
  </div>
</div>
```

### Turboprop Merge

Turboprop Merge allows you to easily merge a list of Tailwind Classes to avoid style conflicts.

Imagine this component:

```elixir
attr :class, :string, doc: "Class override"
def button(assigns) do
  ~H"""
  <button class={["bg-black px-3 py-1.5 text-sm", @class]}>Click me!</button>
  """
end
```

And imagine wanting to make the text a little bigger as a one-off. You've already added a `@class` attribute, but rendering the component
with `class="text-lg"` will lead to an HTML output of `"bg-black px-3 py-1.5 text-sm text-lg"`, with two competing font size classes.

Now, replace the `class` attribute with `class={merge(["bg-black px-3 py-1.5 text-sm", @class])}` and you will magically get
`"bg-black px-3 py-1.5 text-lg"`.

#### Prior art

This type of library exists in the JavaScript world already, in multiple flavors. Turboprop Merge was heavily inspired especially by
[tailwind-merge](https://www.npmjs.com/package/tailwind-merge), so much so that we copied their tests as a starting point.