Feels like I'm just moving from one existential crisis to another. I'm considering removing Alpine from the Perfect Stack. I was frustrated with FOUCE. So I created a dropdown component with Alpine instead of using Shoelace. I liked it at first. Then I started thinking about all the enhancements I'd need to make, like adjusting the position of the dropdown panel if there is not enough space for it, etc. It could get messy. I could break out the Alpine logic into a JS file. But then I lose the colocation advantage that I like about Alpine.
So then I thought maybe I should go back to web components. If I find a decent solution to FOUCE, it might not be too bad. But web components can be a pain to style. IDK. Going to explore that more this week.
It's hard because I like a lot of the DX of JS frameworks, but I don't like a lot of the complexities and abstractions that come with them.
I also ran into some annoying z-index issues this week. I don't totally understand stacking contexts. TL;DR if you have a background color, it will cover a pseudo element with a z-index of -1, unless you use isolation: isolate;
.
Here's my dropdown for reference:
import { html } from 'hono/html';
const TRANSFORM_ORIGINS = {
'bottom left': '.top.left',
'bottom right': '.top.right',
'top left': '.bottom.left',
'top right': '.bottom.right',
};
export default function Dropdown(
items: HtmlContent,
toggleLabel: string | HtmlContent,
position:
| 'bottom left'
| 'bottom right'
| 'top left'
| 'top right' = 'bottom left',
) {
return html`
<div
x-data="{
open: false,
toggle() {
if (this.open) {
return this.close()
}
this.$refs.button.focus()
this.open = true
},
close(focusAfter) {
if (! this.open) return
this.open = false
focusAfter && focusAfter.focus()
}
}"
@keydown.escape.prevent.stop="close($refs.button)"
@focusin.window="!$refs.panel.contains($event.target) && close()"
x-id="['dropdown-button']"
class="dropdown"
>
<div class="actions">
<button
x-ref="button"
@click="toggle()"
:aria-expanded="open"
:aria-controls="$id('dropdown-button')"
type="button"
class="dropdown-toggle"
>
${toggleLabel}
</button>
</div>
<template x-if="open">
<div class="backdrop" @click="close($refs.button)"></div>
</template>
<div
x-ref="panel"
x-show="open"
x-transition.origin.${TRANSFORM_ORIGINS[position]}
@click.outside="close($refs.button)"
:id="$id('dropdown-button')"
x-cloak
class="panel ${position}"
:class="{ open: open }"
>
${items}
</div>
</div>
`;
}