Deku logo

Core concepts

Pursx

Or how I learned to stop worrying and copy-paste the DOM.

Sometimes, you procure a quality snippet of HTML that you would like to throw in a Deku project with minimal hassle. For example, imagine that I get the following chunk of HTML from a designer.

<div class="bg-white">
  <div class="mx-auto max-w-7xl py-12 px-4 text-center sm:px-6 lg:py-16 lg:px-8">
    <h2 class="text-3xl font-bold tracking-tight text-gray-900 sm:text-4xl">
      <span class="block">Ready to dive in?</span>
      <span class="block">Start your free trial today.</span>
    </h2>
    <div class="mt-8 flex justify-center">
      <div class="inline-flex rounded-md shadow">
        <button class="inline-flex items-center justify-center rounded-md border border-transparent bg-indigo-600 px-5 py-3 text-base font-medium text-white hover:bg-indigo-700">Get started</button>
      </div>
      <div class="ml-3 inline-flex">
        <button class="inline-flex items-center justify-center rounded-md border border-transparent bg-indigo-100 px-5 py-3 text-base font-medium text-indigo-700 hover:bg-indigo-200">Learn more</button>
      </div>
    </div>
  </div>
</div>

This renders in the DOM like so.

Ready to dive in? Start your free trial today.

I could meticulously rewrite the entire thing in Deku, at which point my designer would complain to our boss:

This guy is so obsessed with using their pet functional programming language that they spent two hours rewriting HTML snippets in some esoteric format that none of us understand. Can't we just hire a JavaScript developer?

After having been fired from several companies for this exact reason, I developed Pursx, which solves this and many other problems. In this section, you too will learn how to use Pursx (and, by extension, how to keep your job).


A simple example

Let's start with a simple example. The HTML will be a bit on the long side, but the Deku will be short and sweet! As a motivating example, we’ll be developing breadcrumbs.

<nav class="flex" aria-label="Breadcrumb">
  <ol role="list" class="flex space-x-4 rounded-md bg-white px-6 shadow">
    <li class="flex">
      <div class="flex items-center">
        <span class="cursor-pointer text-gray-400 hover:text-gray-500">
          <!-- Heroicon name: mini/home -->
          <svg class="h-5 w-5 flex-shrink-0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" aria-hidden="true">
            <path fill-rule="evenodd" fill="#7393B3" d="M9.293 2.293a1 1 0 011.414 0l7 7A1 1 0 0117 11h-1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-3a1 1 0 00-1-1H9a1 1 0 00-1 1v3a1 1 0 01-1 1H5a1 1 0 01-1-1v-6H3a1 1 0 01-.707-1.707l7-7z" clip-rule="evenodd" />
          </svg>
          <span class="sr-only cursor-pointer">Home</span>
        </span>
      </div>
    </li>

    <li class="flex">
      <div class="flex items-center">
        <svg class="h-full w-6 flex-shrink-0 text-gray-200" viewBox="0 0 24 44" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
          <path fill="#7393B3" d="M.293 0l22 22-22 22h1.414l22-22-22-22H.293z" />
        </svg>
        <span class="cursor-pointer ml-4 text-sm font-medium text-gray-500 hover:text-gray-700">Projects</span>
      </div>
    </li>

    <li class="flex">
      <div class="flex items-center">
        <svg class="h-full w-6 flex-shrink-0 text-gray-200" viewBox="0 0 24 44" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
          <path fill="#7393B3" d="M.293 0l22 22-22 22h1.414l22-22-22-22H.293z" />
        </svg>
        <span class="cursor-pointer ml-4 text-sm font-medium text-gray-500 hover:text-gray-700" aria-current="page">Project Nero</span>
      </div>
    </li>
  </ol>
</nav>

This renders in the DOM like so.

By the end of this page, we’ll have our breadcrumbs hooked up to stateful logic. We'll start by seeing how to render the example above in Deku.

Plain old HTML

Here is the Deku code that produces the breadcrumb example above.

View on GithubVITE_START=PlainOldHtml pnpm example
module Examples.PlainOldHtml where

import Deku.Toplevel (runInBody)
import Effect (Effect)
import Prelude
import ExampleAssitant (ExampleSignature)
import Deku.Toplevel (runInBody)

import Deku.Pursx (pursx)
import Deku.Toplevel (runInBody)

type MyHtml =
  """<nav class="flex" aria-label="Breadcrumb">
  <ol role="list" class="flex space-x-4 rounded-md bg-white px-6 shadow">
    <li class="flex">
      <div class="flex items-center">
        <span class="cursor-pointer text-gray-400 hover:text-gray-500">
          <!-- Heroicon name: mini/home -->
          <svg class="h-5 w-5 flex-shrink-0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" aria-hidden="true">
            <path fill-rule="evenodd" fill="#7393B3" d="M9.293 2.293a1 1 0 011.414 0l7 7A1 1 0 0117 11h-1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-3a1 1 0 00-1-1H9a1 1 0 00-1 1v3a1 1 0 01-1 1H5a1 1 0 01-1-1v-6H3a1 1 0 01-.707-1.707l7-7z" clip-rule="evenodd" />
          </svg>
          <span class="sr-only cursor-pointer">Home</span>
        </span>
      </div>
    </li>

    <li class="flex">
      <div class="flex items-center">
        <svg class="h-full w-6 flex-shrink-0 text-gray-200" viewBox="0 0 24 44" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
          <path fill="#7393B3" d="M.293 0l22 22-22 22h1.414l22-22-22-22H.293z" />
        </svg>
        <span class="cursor-pointer ml-4 text-sm font-medium text-gray-500 hover:text-gray-700">Projects</span>
      </div>
    </li>

    <li class="flex">
      <div class="flex items-center">
        <svg class="h-full w-6 flex-shrink-0 text-gray-200" viewBox="0 0 24 44" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
          <path fill="#7393B3" d="M.293 0l22 22-22 22h1.414l22-22-22-22H.293z" />
        </svg>
        <span class="cursor-pointer ml-4 text-sm font-medium text-gray-500 hover:text-gray-700" aria-current="page">Project Nero</span>
      </div>
    </li>
  </ol>
</nav>"""

main :: Effect Unit
main = void $ runInBody (pursx @MyHtml {})

Just so that you don't miss it, after the large block of HTML, here's the actual Deku bit.

main :: Effect Unit
main = runInBody (myHtml ~~ {})

All you have to do is take your HTML string, add ~~ {} after it, and you get your HTML in the DOM prest-o change-o. In addition to being low-code, this is the most performant Deku gets. It literally takes the code and sets it as the innerHTML of some containing element.

Type safety

Going back to our HTML string, you’ll see that it's actually not a String in the “the type of this term is String” sense of the term. Instead, it's of type Proxy html, where html is a Symbol (aka a type-level String) containing your HTML. That is, the Proxy type constructor is parameterized by your HTML, much as Array can be parameterized by Int or String or Nu Maybe.

By using a type to specify the HTML, Deku can validate it at compile time instead of at runtime. That means you do not need to eat up precious CPU cycles in the browser and you’ll weed out errors earlier. If the HTML isn't valid, your program won't compile.

Let's see a concrete example of this. Say I try to compile the following Deku program with malformed HTML.

main :: Effect Unit
main = runInBody ((Proxy :: Proxy
    "<h1><span>hi<span></h1>") ~~ {})

The compiler complains with the following message.

  Could not match type

    "h1"

  with type

    "span"

While the message could be better by identifying line numbers (we’re working on this…), it identifies the HTML tag mismatch that needs to be corrected. Once we correct it, the program compiles!

Closing tags and tree structure

The Pursx parser gets better all the time, but it does have some limitations. Even though <br> is perfectly valid HTML5, the Pursx parser does not recognize it yet. Instead, you will have to write <br />. Tools like Dreamweaver emit trailing slashes as a default, but you may need to run your HTML through a formatter if these slashes are not present.

At the moment, Pursx needs to be in a single enclosing element. If you have multiple elements, make sure to wrap them in a div or another suitable container. In the future, we may ease this requirement in order to support a list of elements.


Dynamic attributes

Let's bring our Pursx to live by adding some dynamic attributes. We'll modify the breadcrumbs example above with a hook that sets the presence or absence of a crumb based on a click listener.

Adding an attribute

First, let's add a single listener that sets the breadcrumbs' visibiltiy based on interactions with an anchor tag. Here's the code.

View on GithubVITE_START=AddingAnAttributeToPursx pnpm example
module Examples.AddingAnAttributeToPursx where

import Deku.Toplevel (runInBody)
import Effect (Effect)
import Data.Foldable (oneOf)
import Prelude
import ExampleAssitant (ExampleSignature)
import Deku.Toplevel (runInBody)

import Data.Tuple.Nested ((/\))
import Deku.DOM.Attributes as DA
import Deku.Toplevel (runInBody)

import Deku.Control (text_)
import Deku.DOM as D
import Deku.Do as Deku
import Deku.Hooks (useState)
import Deku.DOM.Listeners as DL
import Deku.Pursx (pursx)
import Deku.Toplevel (runInBody)

type MyHtml =
  """<nav class="flex" aria-label="Breadcrumb">
  <ol role="list" class="flex space-x-4 rounded-md bg-white px-6 shadow">
    <li class="flex">
      <div class="flex items-center">
        <span class="cursor-pointer text-gray-400 hover:text-gray-500">
          <!-- Heroicon name: mini/home -->
          <svg class="h-5 w-5 flex-shrink-0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" aria-hidden="true">
            <path fill-rule="evenodd" fill="#7393B3" d="M9.293 2.293a1 1 0 011.414 0l7 7A1 1 0 0117 11h-1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-3a1 1 0 00-1-1H9a1 1 0 00-1 1v3a1 1 0 01-1 1H5a1 1 0 01-1-1v-6H3a1 1 0 01-.707-1.707l7-7z" clip-rule="evenodd" />
          </svg>
          <span class="sr-only cursor-pointer">Home</span>
        </span>
      </div>
    </li>

    <li ~projectsHidden~>
      <div class="flex items-center">
        <svg class="h-full w-6 flex-shrink-0 text-gray-200" viewBox="0 0 24 44" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
          <path fill="#7393B3" d="M.293 0l22 22-22 22h1.414l22-22-22-22H.293z" />
        </svg>
        <span class="cursor-pointer ml-4 text-sm font-medium text-gray-500 hover:text-gray-700">Projects</span>
      </div>
    </li>

    <li ~neroHidden~>
      <div class="flex items-center">
        <svg class="h-full w-6 flex-shrink-0 text-gray-200" viewBox="0 0 24 44" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
          <path fill="#7393B3" d="M.293 0l22 22-22 22h1.414l22-22-22-22H.293z" />
        </svg>
        <span class="cursor-pointer ml-4 text-sm font-medium text-gray-500 hover:text-gray-700" aria-current="page">Project Nero</span>
      </div>
    </li>
  </ol>
</nav>"""

main :: Effect Unit
main = void $ runInBody Deku.do
  setProjects /\ projects <- useState true
  setNero /\ nero <- useState true
  let
    hideOnFalse e =
      DA.klass $ e <#> (if _ then "" else "hidden ") >>>
        (_ <> "flex")
    point = DA.klass_ "cursor-pointer mr-4"
    toggleHome =
      [ point
      , DL.click_ \_ -> (setProjects false *> setNero false)
      ]
    toggleProjects =
      [ point
      , DL.click_ \_ -> (setProjects true *> setNero false)
      ]
    toggleNero =
      [ point
      , DL.click_ \_ -> (setProjects true *> setNero true)
      ]
  D.div_
    [ D.div_
        [ D.a toggleHome [ text_ "Go home" ]
        , D.a toggleProjects [ text_ "Go to projects" ]
        , D.a toggleNero [ text_ "Go to nero" ]
        ]
    , D.div_
        [ pursx @MyHtml
            { projectsHidden: oneOf [ hideOnFalse projects ]
            , neroHidden: oneOf [ hideOnFalse nero ]
            }
        ]
    ]

To specify an attribute in Pursx, we pick an identifier for the attribute and enclose it in tildes. For example, the attribute for the Project Nero list element is ~neroHidden~. Then, when creating the Pursx, we add a field to the record with the name of that identifier followed by whatever attribute we wish to add. In this case, we’re adding a class that hides or shows the breadcrumb.

Adding several attributes

A more natural way to implement the breadcrumbs would be to embed the click listener directly in the crumbs in addition to the anchor elements. We can do this by adding multiple attributes to an element in Pursx.

View on GithubVITE_START=AddingSeveralAttributesToPursx pnpm example
module Examples.AddingSeveralAttributesToPursx where

import Deku.Toplevel (runInBody)
import Prelude
import Data.Foldable (oneOf)
import ExampleAssitant (ExampleSignature)
import Web.PointerEvent.PointerEvent (PointerEvent)
import Deku.Toplevel (runInBody)

import Data.Tuple.Nested ((/\))
import Deku.Attribute (Attribute)
import Deku.DOM.Attributes as DA
import Deku.Toplevel (runInBody)

import Deku.Control (text_)
import Deku.DOM as D
import Deku.Do as Deku
import FRP.Poll (Poll)
import Deku.Hooks (useState)
import Deku.DOM.Listeners as DL
import Effect (Effect)
import Deku.Pursx (pursx)
import Deku.Toplevel (runInBody)

type MyHtml =
  """<nav class="flex" aria-label="Breadcrumb">
  <ol role="list" class="flex space-x-4 rounded-md bg-white px-6 shadow">
    <li ~homeAtts~>
      <div class="flex items-center">
        <span class="cursor-pointer text-gray-400 hover:text-gray-500">
          <!-- Heroicon name: mini/home -->
          <svg class="h-5 w-5 flex-shrink-0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" aria-hidden="true">
            <path fill-rule="evenodd" fill="#7393B3" d="M9.293 2.293a1 1 0 011.414 0l7 7A1 1 0 0117 11h-1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-3a1 1 0 00-1-1H9a1 1 0 00-1 1v3a1 1 0 01-1 1H5a1 1 0 01-1-1v-6H3a1 1 0 01-.707-1.707l7-7z" clip-rule="evenodd" />
          </svg>
          <span class="sr-only cursor-pointer">Home</span>
        </span>
      </div>
    </li>

    <li ~projectsAtts~>
      <div class="flex items-center">
        <svg class="h-full w-6 flex-shrink-0 text-gray-200" viewBox="0 0 24 44" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
          <path fill="#7393B3" d="M.293 0l22 22-22 22h1.414l22-22-22-22H.293z" />
        </svg>
        <span class="cursor-pointer ml-4 text-sm font-medium text-gray-500 hover:text-gray-700">Projects</span>
      </div>
    </li>

    <li ~neroAtts~>
      <div class="flex items-center">
        <svg class="h-full w-6 flex-shrink-0 text-gray-200" viewBox="0 0 24 44" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
          <path fill="#7393B3" d="M.293 0l22 22-22 22h1.414l22-22-22-22H.293z" />
        </svg>
        <span class="cursor-pointer ml-4 text-sm font-medium text-gray-500 hover:text-gray-700" aria-current="page">Project Nero</span>
      </div>
    </li>
  </ol>
</nav>"""

main :: Effect Unit
main = void $ runInBody Deku.do
  setProjects /\ projects <- useState true
  setNero /\ nero <- useState true
  let
    hideOnFalse e =
      DA.klass $ e <#> (if _ then "" else "hidden ") >>>
        (_ <> "flex")

    toggleHome
      :: forall r
       . Poll (Attribute (click :: PointerEvent | r))
    toggleHome = DL.click_ \_ -> (setProjects false *> setNero false)

    toggleProjs
      :: forall r
       . Poll (Attribute (click :: PointerEvent | r))
    toggleProjs = DL.click_ \_ -> (setProjects true *> setNero false)

    toggleNero
      :: forall r
       . Poll (Attribute (click :: PointerEvent | r))
    toggleNero = DL.click_ \_ -> (setProjects false *> setNero true)

    akls = append [ DA.klass_ "cursor-pointer mr-4" ] <<< pure
  D.div_
    [ D.div_
        [ D.a (akls toggleHome) [ text_ "Go home" ]
        , D.a (akls toggleProjs) [ text_ "Go to projects" ]
        , D.a (akls toggleNero) [ text_ "Go to nero" ]
        ]
    , D.div_
        [ pursx @MyHtml
            { homeAtts: oneOf [ toggleHome, DA.klass_ "flex h-12" ]
            , projectsAtts: oneOf [ toggleProjs, hideOnFalse projects ]
            , neroAtts: oneOf [ toggleNero, hideOnFalse nero ]
            }
        ]
    ]

By using the tie fighter <|>, we were able to compose the breadcrumb's class and its click listener together, just like if we were working with attributes in a Deku component. This allows you to mix Pursx code and Deku components.

No let polymorphism

PureScript lacks let polymorphism, meaning that effects like toggleHome and toggleNero need an explicit polymorphic signature when used for different DOM elements. Otherwise, they would be specialized to their first call site, which in this case is an a tag. That's why we use explicit signatures for the toggle effects.


Dynamic elements

Now that we've added dynamic attributes, let's add dynamic elements to our Pursx.

Adding a single DOM element as a component

One natural case for adding elements to Pursx is when dealing with components that are easy to group together. For example, we can rewrite our breadcrumbs example to reuse the li element.

View on GithubVITE_START=AddingASingleElementToPursx pnpm example
module Examples.AddingASingleElementToPursx where

import Deku.Toplevel (runInBody)
import Prelude
import Data.Foldable (oneOf)
import ExampleAssitant (ExampleSignature)
import Web.PointerEvent.PointerEvent (PointerEvent)
import Deku.Toplevel (runInBody)

import Data.Tuple.Nested ((/\))
import Deku.Attribute (Attribute)
import Deku.DOM.Attributes as DA
import Deku.Toplevel (runInBody)

import Deku.Control (text_)
import Deku.DOM as D
import FRP.Poll (Poll)
import Deku.Do as Deku
import Deku.Hooks (useState)
import Deku.DOM.Listeners as DL
import Deku.Pursx (pursx)
import Effect (Effect)
import Deku.Toplevel (runInBody)

type LiHtml =
  """<li ~atts~>
      <div class="flex items-center">
        <svg class="h-full w-6 flex-shrink-0 text-gray-200" viewBox="0 0 24 44" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
          <path fill="#7393B3" d="M.293 0l22 22-22 22h1.414l22-22-22-22H.293z" />
        </svg>
        <span class="cursor-pointer ml-4 text-sm font-medium text-gray-500 hover:text-gray-700" aria-current="page">~name~</span>
      </div>
    </li>"""

type MyHtml =
  """<nav class="flex" aria-label="Breadcrumb">
  <ol role="list" class="flex space-x-4 rounded-md bg-white px-6 shadow">
    <li ~homeAtts~>
      <div class="flex items-center">
        <span class="cursor-pointer text-gray-400 hover:text-gray-500">
          <!-- Heroicon name: mini/home -->
          <svg class="h-5 w-5 flex-shrink-0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" aria-hidden="true">
            <path fill-rule="evenodd" fill="#7393B3" d="M9.293 2.293a1 1 0 011.414 0l7 7A1 1 0 0117 11h-1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-3a1 1 0 00-1-1H9a1 1 0 00-1 1v3a1 1 0 01-1 1H5a1 1 0 01-1-1v-6H3a1 1 0 01-.707-1.707l7-7z" clip-rule="evenodd" />
          </svg>
          <span class="sr-only cursor-pointer">Home</span>
        </span>
      </div>
    </li>

    ~projectLi~
    ~neroLi~
  </ol>
</nav>"""

main :: Effect Unit
main = void $ runInBody Deku.do
  setProjects /\ projects <- useState true
  setNero /\ nero <- useState true
  let
    hideOnFalse e =
      DA.klass $ e <#> (if _ then "" else "hidden ") >>>
        (_ <> "flex")

    toggleHome
      :: forall r
       . Poll (Attribute (click :: PointerEvent | r))
    toggleHome = DL.click_ \_ -> (setProjects false *> setNero false)

    toggleProjs
      :: forall r
       . Poll (Attribute (click :: PointerEvent | r))
    toggleProjs = DL.click_ \_ -> (setProjects true *> setNero false)

    toggleNero
      :: forall r
       . Poll (Attribute (click :: PointerEvent | r))
    toggleNero = DL.click_ \_ -> (setProjects false *> setNero true)

  D.div_
    [ D.div_
        [ D.a [ DA.klass_ "cursor-pointer mr-4", toggleHome ]
            [ text_ "Go home" ]
        , D.a [ DA.klass_ "cursor-pointer mr-4", toggleProjs ]
            [ text_ "Go to projects" ]
        , D.a [ DA.klass_ "cursor-pointer", toggleNero ]
            [ text_ "Go to nero" ]
        ]
    , D.div_
        [ pursx @MyHtml
            { homeAtts: oneOf [ toggleHome, DA.klass_ "flex h-12" ]
            , projectLi:
                pursx @LiHtml
                  { atts: oneOf [ toggleProjs, hideOnFalse projects ]
                  , name: text_ "Projects"
                  }
            , neroLi:
                pursx @LiHtml
                  { atts: oneOf [ toggleNero, hideOnFalse nero ]
                  , name: text_ "Project Nero"
                  }
            }
        ]
    ]

To add an element dynamically, we use the same method as adding a dynamic attribute. Just place it between tildes wherever you need it!

Adding several DOM elements as a component

To add several elements, one can use fixed or <>.

View on GithubVITE_START=AddingSeveralElementsToPursx pnpm example
module Examples.AddingSeveralElementsToPursx where

import Deku.Toplevel (runInBody)
import Prelude
import Web.PointerEvent.PointerEvent (PointerEvent)
import FRP.Poll (Poll)
import ExampleAssitant (ExampleSignature)
import Data.Foldable (oneOf)
import Data.Tuple.Nested ((/\))
import Deku.DOM.Attributes as DA
import Deku.Attribute (Attribute)
import Deku.Control (text_)
import Deku.Core (fixed)
import Deku.DOM as D
import Deku.Do as Deku
import Deku.Hooks (useState)
import Effect (Effect)
import Deku.DOM.Listeners as DL
import Deku.Pursx (pursx)
import Deku.Toplevel (runInBody)

type LiHtml =
  """<li ~atts~>
      <div class="flex items-center">
        <svg class="h-full w-6 flex-shrink-0 text-gray-200" viewBox="0 0 24 44" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
          <path fill="#7393B3" d="M.293 0l22 22-22 22h1.414l22-22-22-22H.293z" />
        </svg>
        <span class="cursor-pointer ml-4 text-sm font-medium text-gray-500 hover:text-gray-700" aria-current="page">~name~</span>
      </div>
    </li>"""

type MyHtml =
  """<nav class="flex" aria-label="Breadcrumb">
  <ol role="list" class="flex space-x-4 rounded-md bg-white px-6 shadow">
    <li ~homeAtts~>
      <div class="flex items-center">
        <span class="cursor-pointer text-gray-400 hover:text-gray-500">
          <!-- Heroicon name: mini/home -->
          <svg class="h-5 w-5 flex-shrink-0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" aria-hidden="true">
            <path fill-rule="evenodd" fill="#7393B3" d="M9.293 2.293a1 1 0 011.414 0l7 7A1 1 0 0117 11h-1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-3a1 1 0 00-1-1H9a1 1 0 00-1 1v3a1 1 0 01-1 1H5a1 1 0 01-1-1v-6H3a1 1 0 01-.707-1.707l7-7z" clip-rule="evenodd" />
          </svg>
          <span class="sr-only cursor-pointer">Home</span>
        </span>
      </div>
    </li>

    ~lis~
  </ol>
</nav>"""

main :: Effect Unit
main = void $ runInBody Deku.do
  setProjects /\ projects <- useState true
  setNero /\ nero <- useState true
  let
    hideOnFalse e =
      DA.klass $ e <#> (if _ then "" else "hidden ") >>>
        (_ <> "flex")

    toggleHome
      :: forall r
       . Poll (Attribute (click :: PointerEvent | r))
    toggleHome = DL.click_ \_ -> (setProjects false *> setNero false)

    toggleProjs
      :: forall r
       . Poll (Attribute (click :: PointerEvent | r))
    toggleProjs = DL.click_ \_ -> (setProjects true *> setNero false)

    toggleNero
      :: forall r
       . Poll (Attribute (click :: PointerEvent | r))
    toggleNero = DL.click_ \_ -> (setProjects false *> setNero true)

  D.div_
    [ D.div_
        [ D.a [ DA.klass_ "cursor-pointer mr-4", toggleHome ]
            [ text_ "Go home" ]
        , D.a
            [ DA.klass_ "cursor-pointer mr-4", toggleProjs ]
            [ text_ "Go to projects" ]
        , D.a [ DA.klass_ "cursor-pointer", toggleNero ]
            [ text_ "Go to nero" ]
        ]
    , D.div_
        [ pursx @MyHtml
            { homeAtts: oneOf [ toggleHome, DA.klass_ "flex h-12" ]
            , lis: fixed
                [ pursx @LiHtml
                    { atts: oneOf
                        [ toggleProjs
                        , hideOnFalse projects
                        ]
                    , name: text_ "Projects"
                    }
                , pursx @LiHtml
                    { atts: oneOf
                        [ toggleNero, hideOnFalse nero ]
                    , name: text_ "Project Nero"
                    }
                ]
            }
        ]
    ]