124 lines
3.3 KiB
Svelte
124 lines
3.3 KiB
Svelte
<svelte:options
|
|
customElement={{
|
|
// `shadowRoot` field must remain as the default, else named slots break.
|
|
props: {
|
|
button_aria_label: { attribute: "button-aria-label" },
|
|
alignment: { attribute: "alignment" },
|
|
},
|
|
tag: "basic-dropdown",
|
|
}}
|
|
/>
|
|
|
|
<!--
|
|
@component
|
|
|
|
A button with an associated popover, which can be styled for a variety of
|
|
purposes, such as menus, confirmation popups, and so on.
|
|
|
|
When used as a web component, this component is rendered with a shadow root in
|
|
order to correctly support named slots. As a result, it bundles its own
|
|
stylesheet, which is merely a copy of the "main" application stylesheet.
|
|
|
|
## Props
|
|
|
|
- `alignment`: When set to "right", the popover aligns with the right edge of
|
|
the trigger. Otherwise, it aligns with the left.
|
|
-->
|
|
|
|
<script lang="ts">
|
|
type Props = {
|
|
alignment?: string;
|
|
button_aria_label?: string;
|
|
on_toggle?(ev: ToggleEvent): unknown;
|
|
};
|
|
|
|
let { alignment, button_aria_label, on_toggle }: Props = $props();
|
|
|
|
let popover_element: HTMLElement | undefined = $state();
|
|
// Hacky workaround because as of September 2025 implicit anchor association
|
|
// is still pretty broken, at least in Firefox.
|
|
let anchor_name = $state(`--anchor-${Math.floor(Math.random() * 1000000)}`);
|
|
|
|
let popover_left = $derived(
|
|
alignment?.toLocaleLowerCase("en-US") === "right"
|
|
? "unset"
|
|
: "anchor(left)",
|
|
);
|
|
let popover_right = $derived(
|
|
alignment?.toLocaleLowerCase("en-US") === "right"
|
|
? "anchor(right)"
|
|
: "unset",
|
|
);
|
|
|
|
$effect(() => {
|
|
if (on_toggle) {
|
|
popover_element?.addEventListener("toggle", on_toggle);
|
|
return () => {
|
|
popover_element?.removeEventListener("toggle", on_toggle);
|
|
};
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<button
|
|
aria-label={button_aria_label}
|
|
class="basic-dropdown__button"
|
|
id="dropdown-button"
|
|
onclick={() => {
|
|
popover_element?.showPopover();
|
|
}}
|
|
style:anchor-name={anchor_name}
|
|
type="button"
|
|
>
|
|
<slot name="button-contents"></slot>
|
|
</button>
|
|
<div
|
|
aria-labelledby="dropdown-button"
|
|
bind:this={popover_element}
|
|
class="basic-dropdown__popover"
|
|
style:left={popover_left}
|
|
style:position-anchor={anchor_name}
|
|
style:right={popover_right}
|
|
popover="auto"
|
|
>
|
|
<slot name="popover"></slot>
|
|
</div>
|
|
|
|
<style lang="css">
|
|
.basic-dropdown__button {
|
|
appearance: none;
|
|
box-sizing: border-box;
|
|
cursor: pointer;
|
|
font-weight: inherit;
|
|
|
|
background: var(--button-background);
|
|
border: solid 1px var(--button-border-color);
|
|
border-radius: var(--button-border-radius);
|
|
box-shadow: var(--button-shadow);
|
|
color: var(--button-color);
|
|
font-family: var(--button-font-family);
|
|
font-size: var(--button-font-size);
|
|
padding: var(--button-padding);
|
|
text-decoration: none;
|
|
transition: background 0.2s ease;
|
|
|
|
&:hover {
|
|
border-color: oklch(from var(--button-border-color) calc(l * 0.95) c h);
|
|
background: oklch(from var(--button-background) calc(l * 0.95) c h);
|
|
}
|
|
}
|
|
|
|
.basic-dropdown__popover:popover-open {
|
|
background: #fff;
|
|
border: solid 1px var(--popover-border-color);
|
|
border-radius: var(--default-border-radius--rounded);
|
|
box-shadow: var(--popover-shadow);
|
|
display: block;
|
|
margin: unset;
|
|
max-height: 90vh;
|
|
overflow: auto;
|
|
padding: 0;
|
|
position: absolute;
|
|
top: anchor(bottom);
|
|
}
|
|
</style>
|