You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/uix/eidos/components/button/button.svelte

90 lines
2.9 KiB

<script lang="ts">
import './button.css';
/**
* Eidos `<Button>` — visual wrapper over Soma's headless button.
*
* Soma owns the click flow, `data-state`, `aria-busy`, `aria-disabled`
* and the `contact-activate` perceptual event (book cap. 22 §10).
* Eidos adds the visual API on top — `variant`, `size`, `rounded`,
* `block`, `iconOnly`, the `icon` / `endIcon` snippet slots, and the
* loading Spinner — and wires those to the `data-*` selectors that
* `button.css` reacts to.
*/
import { ActiveEidos } from '$uix/eidos';
import * as Button from '$soma/components/button';
import ButtonSpinner from './button-spinner.svelte';
import type { ButtonProps } from './types';
let {
variant = 'solid',
size = 'md',
rounded,
shape,
block = false,
iconOnly = false,
loading = false,
icon,
endIcon,
spinner,
spinnerPlacement = 'start',
loadingText,
children: bodyContent,
...headlessProps
}: ButtonProps = $props();
const eidos = ActiveEidos.require();
const resolvedSize = $derived(eidos.resolve(size, 'md'));
const resolvedRounded = $derived(eidos.resolve(rounded));
</script>
<Button.Provider
{...headlessProps}
{loading}
{loadingText}
{spinnerPlacement}
data-variant={variant}
data-size={resolvedSize}
data-rounded={resolvedRounded ?? undefined}
data-shape={shape}
data-block={block ? '' : undefined}
data-icon-only={iconOnly ? '' : undefined}
data-has-icon={icon || (loading && spinnerPlacement === 'start') ? '' : undefined}
data-spinner-placement={spinnerPlacement}
>
{#snippet children(snippetProps)}
<!-- Start slot: spinner (when loading) wins over icon at start placement. -->
{#if loading && spinnerPlacement === 'start'}
{#if spinner}<ButtonSpinner>{@render spinner()}</ButtonSpinner>{:else}<ButtonSpinner />{/if}
{:else if icon}
<span class="eidos-button-icon" data-button-icon="start" aria-hidden="true">
{@render icon(snippetProps)}
</span>
{/if}
<!--
* Body wrapper holds the text label. When `iconOnly` is on, the
* recipe makes it visually-hidden but keeps it in the accessibility
* tree so screen readers still announce the label. When loading
* with `loadingText`, the original children remain in DOM (sr-only)
* and the loadingText takes the visual slot.
-->
<span class="eidos-button-body">
{#if loading && loadingText}
<span class="eidos-button-loading-text">{loadingText}</span>
<span class="eidos-button-sr-only">{@render bodyContent?.(snippetProps)}</span>
{:else}
{@render bodyContent?.(snippetProps)}
{/if}
</span>
<!-- End slot: spinner (when loading) wins over endIcon at end placement. -->
{#if loading && spinnerPlacement === 'end'}
{#if spinner}<ButtonSpinner>{@render spinner()}</ButtonSpinner>{:else}<ButtonSpinner />{/if}
{:else if endIcon}
<span class="eidos-button-icon" data-button-icon="end" aria-hidden="true">
{@render endIcon(snippetProps)}
</span>
{/if}
{/snippet}
</Button.Provider>

Powered by TurnKey Linux.