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

86 lines
2.8 KiB

feat(eidos): Button + 9 demo rebuilds + picker recipe polish Build: - New <Button> compound (morfo + soma + sema + eidos): 6 variants (solid/soft/surface/outline/ghost/plain), 8 colors, 5 sizes, optional icon/endIcon snippets, loading state with built-in Spinner, asChild Slot pattern, intent-driven sema (commit-action with fromProp:intent). Demo rebuilds (canonical depth per DEMO_AUTHORING_GUIDE): - context-menu, dropdown-menu, pin-input, alert-dialog, toggle-group: full per-part Morfo contracts, reference-parity tables, ARIA contract tables, keyboard tables, composition controls. - color-field, time-field, time-picker, time-range-picker, color-picker, month-grid, year-grid: same canonical treatment; per-part data/aria/ keyboard iteration of raw morfo, reference parity vs react-aria/ ark-ui/Chakra/bits-ui/Mantine. Picker recipe polish: - PickerShell.Clear/Cancel/Close refactored to use <Button size="xs"> (affects 5 pickers — date / date-range / time / time-range / color). - TimePicker + TimeRangePicker: popover condensed (min-width 18rem -> 14 / 32 -> 28), per-channel accent (hour=primary, minute=secondary, second=neutral; end variant muted), ruler off by default, hour ruler cleaned to 0/6/12/18/23. - ColorPicker: typography token --_color-picker-trigger-font-size that scales with data-size (10/12/14px); autoFormat default true; Area recipe override for [data-x-channel='hue'] with rainbow gradient + saturation/brightness Y overlays. - ValueText eidos wrapper: fix children snippet bug that suppressed soma's displayValue fallback. - date-field.css: segment editing affordance (cursor + hover + active ring) via new --_date-field-accent-element token across 8 color cascades. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
<script lang="ts">
/**
* Eidos `<Button>` — visual wrapper over Soma's headless button.
*
* Soma owns the click flow, `data-state`, `aria-busy`, `aria-disabled`
* and the `commit-action` perceptual event. 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,
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-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.