<script module lang="ts">
import type { Component } from "svelte";
import type { SvelteHTMLElements } from "svelte/elements";
export interface SplitButtonOption {
/** shown in the menu, where the options are read side by side and need to disambiguate */
label: string;
/** shown on the button once armed; falls back to `label` when the two can be the same */
buttonLabel?: string;
icon?: Component<SvelteHTMLElements["svg"]>;
onclick?: () => void;
danger?: boolean;
}
</script>
<script lang="ts">
import ChevronDown from "$icon/chevron-down";
import Button, { buttonIcon, type ButtonVariants } from "#lib/components/ui/Button.svelte";
import ButtonGroup from "#lib/components/ui/ButtonGroup.svelte";
import Dropdown, {
type DropdownProps,
type DropdownVariants
} from "#lib/components/ui/Dropdown.svelte";
import DropdownItem from "#lib/components/ui/DropdownItem.svelte";
interface Props {
options: SplitButtonOption[];
/** index of the armed option; two-way, so a caller can react to the choice */
selected?: number;
variant?: ButtonVariants["variant"];
size?: ButtonVariants["size"];
align?: DropdownVariants["align"];
side?: DropdownProps["side"];
menuLabel?: string;
disabled?: boolean;
loading?: boolean;
class?: string;
testid?: string;
}
let {
options,
selected = $bindable(0),
variant = "default",
size,
align = "right",
side,
menuLabel = "More options",
disabled = false,
loading = false,
class: className,
testid
}: Props = $props();
// options can be built from a filtered list, so neither an empty array nor a
// stale index may reach options[selected]
const current = $derived(options[Math.min(Math.max(selected, 0), options.length - 1)]);
</script>
{#if current}
<ButtonGroup class={className} data-testid={testid}>
<Button
data-testid="primaryAction"
class="flex-1"
{variant}
{size}
{disabled}
{loading}
icon={current.icon}
onclick={current.onclick}
>
<span>{current.buttonLabel ?? current.label}</span>
</Button>
<!-- the menu stays open even when the armed option is disabled, so a caller
can gate one option (a conflicting merge, say) without trapping the user -->
<Dropdown
variant="button"
square
buttonVariant={variant}
{size}
{align}
{side}
label={menuLabel}
testid="options"
>
{#snippet trigger()}
<ChevronDown class={buttonIcon({ size })} />
{/snippet}
{#each options as option, index (option.label)}
<DropdownItem
data-testid="option"
data-index={index}
icon={option.icon}
danger={option.danger}
{size}
onclick={() => (selected = index)}
>
{option.label}
</DropdownItem>
{/each}
</Dropdown>
</ButtonGroup>
{/if}