refactor(dropdown-menu): remove unused modal; Trigger→Button; canon vocab fixes

Three related fixes touching the menu surface:

1. **Remove `modal` from DropdownMenu** (#127). The prop was declared in
   soma but never wired to anything — no overlay, no pointer-events
   blocker, no scrim. ark-ui / Headless-UI / Chakra all treat menu as
   non-modal by convention; Dialog/Drawer/AlertDialog cover blocking
   semantics. Drop it from soma (types, provider opts, component, test
   fixture), the eidos doc comment, the menubar consumer, and the
   dropdown-menu demo (state, control, snippet, API table, parity table).

2. **DropdownMenu.Trigger now renders `<Button>` internally** (#120
   first consumer). Default variant='surface', size='md', intent='neutral'.
   `variant` is narrowed to ControlVariant (`'surface' | 'outline' | 'ghost'`)
   per DEMO_AUTHORING_GUIDE §12.7 so the trigger stays consistent with
   the form-control surface (Field/Select/Combobox) instead of inheriting
   Button's wider 6-variant set. The eidos demo exposes variant/size/intent
   chips that match the canonical narrowed vocab (Intent: neutral · affirm
   · fulfill · risk · threat · loss). Trigger's per-recipe CSS dropped —
   chrome comes from button.css.

3. **Menu-item unchecked indicators** (follow-up to #118). CheckboxItem
   and RadioItem now paint an empty outlined box / circle even when
   unchecked, so the user can tell at a glance which rows are checkable.
   Checked state fills with the accent color via the same `::before`. The
   reserved padding slot (26px) keeps row text from shifting when state
   flips.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
active-uix
dev 5 months ago
parent 0e9628b8f0
commit 4b5bd9f35d

@ -1,7 +1,64 @@
<script lang="ts"> <script lang="ts">
/**
* Eidos DropdownMenu.Trigger.
*
* Renders the soma Trigger via its `child` snippet so the inner element
* is an eidos `<Button>`. The Trigger picks up Button's chrome (focus
* ring, padding, intent/color, variant) for free; the soma layer still
* owns the click flow, ARIA wiring, and `data-state`. The
* `[data-dropdown-menu-trigger]` data-attr lands on the same `<button>`
* Button emits, so the recipe selector still matches.
*
* Visual props (`variant`/`size`/`intent`/`color`/etc.) are forwarded
* to Button. `variant` is narrowed to ControlVariant
* (`'surface' | 'outline' | 'ghost'`) so the trigger stays consistent
* with the rest of the form-control surface — see types.ts for the
* narrowing rationale. Consumers override the standard Button visual
* props inline:
*
* <DropdownMenu.Trigger variant="solid" intent="affirm">Save</DropdownMenu.Trigger>
*
* Composition: callers can still pass their own `child` snippet to
* bypass Button entirely (e.g. wrap an Avatar). When `child` is passed
* we forward straight through soma and skip Button.
*/
import * as DropdownMenu from '$soma/components/dropdown-menu'; import * as DropdownMenu from '$soma/components/dropdown-menu';
import { Button } from '$uix/eidos/components/button';
import type { DropdownMenuTriggerProps } from './types'; import type { DropdownMenuTriggerProps } from './types';
let { children, ...rest }: DropdownMenuTriggerProps = $props();
let {
children,
child: outerChild,
variant = 'surface',
size = 'md',
rounded,
intent,
color,
iconOnly,
icon,
endIcon,
...rest
}: DropdownMenuTriggerProps = $props();
</script> </script>
<DropdownMenu.Trigger {...rest}>{@render children?.()}</DropdownMenu.Trigger> <DropdownMenu.Trigger {...rest}>
{#snippet child({ props })}
{#if outerChild}
{@render outerChild({ props })}
{:else}
<Button
{...props}
{variant}
{size}
{rounded}
{intent}
{color}
{iconOnly}
{icon}
{endIcon}
>
{@render children?.()}
</Button>
{/if}
{/snippet}
</DropdownMenu.Trigger>

@ -129,16 +129,12 @@
background: var(--dropdown-menu-separator-bg, var(--color-border-subtle)); background: var(--dropdown-menu-separator-bg, var(--color-border-subtle));
} }
/* ── Trigger (button-ish, but bring-your-own visual) ──────────────── */ /* ── Trigger ──────────────────────────────────────────────────────── */
/*
[data-dropdown-menu-trigger] { * The Trigger is rendered as an eidos `<Button>` (variant='surface' by
cursor: pointer; * default) — chrome, cursor, focus ring, padding all come from button.css.
} * Consumers override via the standard Button visual props.
*/
[data-dropdown-menu-trigger]:focus-visible {
outline: none;
box-shadow: var(--focus-ring);
}
/* ── Arrow ────────────────────────────────────────────────────────── */ /* ── Arrow ────────────────────────────────────────────────────────── */

@ -6,7 +6,7 @@
// CheckboxItem / RadioGroup+RadioItem and nested Sub-menus. // CheckboxItem / RadioGroup+RadioItem and nested Sub-menus.
// //
// Soma owns positioning (Side / Align), keyboard navigation (arrows, // Soma owns positioning (Side / Align), keyboard navigation (arrows,
// typeahead, escape), focus trap, modal/dismissal behaviour. Eidos // typeahead, escape), focus trap, dismissal behaviour. Eidos
// adds the recipe chrome. // adds the recipe chrome.
// //
// All props pass through from soma except where explicitly noted — // All props pass through from soma except where explicitly noted —
@ -30,9 +30,57 @@ import type {
SubTriggerProps, SubTriggerProps,
SubContentProps SubContentProps
} from '$soma/components/dropdown-menu'; } from '$soma/components/dropdown-menu';
import type {
ButtonVariant,
ButtonSize,
ButtonRounded,
ButtonProps as EidosButtonProps
} from '$uix/eidos/components/button';
import type { ControlVariant, ResponsiveProp } from '$uix/eidos/lib/types';
/**
* DropdownMenu.Trigger narrows ButtonVariant down to ControlVariant
* (`'surface' | 'outline' | 'ghost'`) — the canonical 3-set used by
* form-control components. The Trigger conceptually IS a form control
* (opens a menu), so it stays consistent with Field, Select, Combobox,
* etc. instead of inheriting Button's wider 6-variant surface.
*
* Narrowing rationale per DEMO_AUTHORING_GUIDE §12.7 (chip parity).
*/
export type DropdownMenuTriggerVariant = Extract<ButtonVariant, ControlVariant>;
export type DropdownMenuProps = ProviderProps; export type DropdownMenuProps = ProviderProps;
export type DropdownMenuTriggerProps = TriggerProps;
/**
* DropdownMenu.Trigger props. Extends soma's headless TriggerProps with
* a narrowed subset of Button visual props so the trigger paints as a
* real button by default (variant='surface', neutral intent), but stays
* visually consistent with the rest of the form-control surface
* (Field, Select, Combobox, etc.) — see ControlVariant narrowing above.
*
* Behavioral props (id, ref, ARIA, child snippet, onclick…) come from
* soma. Visual props (variant/size/rounded/intent/color/iconOnly/icon/
* endIcon) come from Button. Passing `child` opts out of Button entirely
* and forwards composition straight through soma.
*/
export type DropdownMenuTriggerProps = TriggerProps & {
/** Button visual variant, narrowed to ControlVariant. @default 'surface' */
variant?: DropdownMenuTriggerVariant;
/** Button size scale. @default 'md' */
size?: ResponsiveProp<ButtonSize>;
/** Button corner radius. */
rounded?: ResponsiveProp<ButtonRounded>;
/** Evaluative intent — flows to Button. */
intent?: EidosButtonProps['intent'];
/** Hierarchical color override (only applies under intent='neutral'). */
color?: EidosButtonProps['color'];
/** Square aspect with sr-only label; use `icon` for the visible glyph. */
iconOnly?: boolean;
/** Leading icon snippet, visible even when iconOnly is on. */
icon?: EidosButtonProps['icon'];
/** Trailing icon snippet; collapsed when iconOnly is on. */
endIcon?: EidosButtonProps['endIcon'];
};
export type DropdownMenuContentProps = ContentProps; export type DropdownMenuContentProps = ContentProps;
export type DropdownMenuArrowProps = ArrowProps; export type DropdownMenuArrowProps = ArrowProps;
export type DropdownMenuItemProps = ItemProps; export type DropdownMenuItemProps = ItemProps;

@ -41,6 +41,7 @@
--_menu-indicator-size: 0.875rem; --_menu-indicator-size: 0.875rem;
--_menu-indicator-radius: 0.2rem; --_menu-indicator-radius: 0.2rem;
--_menu-indicator-color: var(--color-primary-solid); --_menu-indicator-color: var(--color-primary-solid);
--_menu-indicator-border: var(--color-border-default);
--_menu-indicator-inset: var(--space-2); --_menu-indicator-inset: var(--space-2);
position: relative; position: relative;
@ -49,24 +50,38 @@
); );
} }
/* The indicator itself — only painted when state is checked. */ /* Base indicator: always painted (regardless of state) so the user can
[data-context-menu-checkbox-item][data-state='checked']::before, tell at a glance that the row IS a checkbox / radio item. The empty
[data-context-menu-radio-item][data-state='checked']::before, shape is just an outlined box / circle; the filled shape appears once
[data-dropdown-menu-checkbox-item][data-state='checked']::before, the state flips to `checked`. */
[data-dropdown-menu-radio-item][data-state='checked']::before { [data-context-menu-checkbox-item]::before,
[data-context-menu-radio-item]::before,
[data-dropdown-menu-checkbox-item]::before,
[data-dropdown-menu-radio-item]::before {
content: ''; content: '';
position: absolute; position: absolute;
inset-inline-start: var(--_menu-indicator-inset); inset-inline-start: var(--_menu-indicator-inset);
inline-size: var(--_menu-indicator-size); inline-size: var(--_menu-indicator-size);
block-size: var(--_menu-indicator-size); block-size: var(--_menu-indicator-size);
border: 1px solid var(--_menu-indicator-border);
border-radius: var(--_menu-indicator-radius); border-radius: var(--_menu-indicator-radius);
background: transparent;
transition: background var(--duration-fast) var(--ease-default);
}
/* Checked state — fill the indicator with the accent color. */
[data-context-menu-checkbox-item][data-state='checked']::before,
[data-context-menu-radio-item][data-state='checked']::before,
[data-dropdown-menu-checkbox-item][data-state='checked']::before,
[data-dropdown-menu-radio-item][data-state='checked']::before {
border-color: var(--_menu-indicator-color);
background: var(--_menu-indicator-color); background: var(--_menu-indicator-color);
mask-image: var(--_menu-indicator-mask, none); mask-image: var(--_menu-indicator-mask, none);
-webkit-mask-image: var(--_menu-indicator-mask, none); -webkit-mask-image: var(--_menu-indicator-mask, none);
} }
/* Radio variant — circular indicator. */ /* Radio variant — circular indicator (both unchecked and checked). */
[data-context-menu-radio-item][data-state='checked']::before, [data-context-menu-radio-item]::before,
[data-dropdown-menu-radio-item][data-state='checked']::before { [data-dropdown-menu-radio-item]::before {
border-radius: 50%; border-radius: 50%;
} }

@ -182,7 +182,7 @@ No standalone functions (`createApp`, `getApp`, `useApp`). No `from()`. The `ctx
- If a handler needs a context reference, capture it in the constructor. - If a handler needs a context reference, capture it in the constructor.
- Event handlers (onclick, onkeydown) must be included in `props`. Defining them as class methods without spreading them into props means they won't reach the DOM. - Event handlers (onclick, onkeydown) must be included in `props`. Defining them as class methods without spreading them into props means they won't reach the DOM.
- Layers (Presence, FocusScope, Dismissal, Gesture, etc.) are instantiated in the constructor and their `.props` are spread into the component's `props`. - Layers (Presence, FocusScope, Dismissal, Gesture, etc.) are instantiated in the constructor and their `.props` are spread into the component's `props`.
- For DropdownMenu: `interactOutsideBehavior` defaults to `'close'` (not inherited from `modal` like Dialog). - For DropdownMenu / ContextMenu: `interactOutsideBehavior` defaults to `'close'`. Menus are intentionally non-modal — for blocking semantics use Dialog/Drawer/AlertDialog.
### Types: define once, reference in Opts ### Types: define once, reference in Opts

@ -17,7 +17,6 @@
open = $bindable(false), open = $bindable(false),
onOpenChange = () => {}, onOpenChange = () => {},
onOpenChangeComplete = () => {}, onOpenChangeComplete = () => {},
modal = true,
dir, dir,
accessibleWhenDisabled = true, accessibleWhenDisabled = true,
children children
@ -33,7 +32,6 @@
} }
), ),
onOpenChangeComplete: readableActive(() => onOpenChangeComplete), onOpenChangeComplete: readableActive(() => onOpenChangeComplete),
modal: readableActive(() => modal),
dir: readableActive(() => dir ?? soma?.prefs.getDir() ?? 'ltr'), dir: readableActive(() => dir ?? soma?.prefs.getDir() ?? 'ltr'),
accessibleWhenDisabled: readableActive(() => accessibleWhenDisabled) accessibleWhenDisabled: readableActive(() => accessibleWhenDisabled)
}); });

@ -81,7 +81,6 @@ function createMenuOpts() {
id: state('dropdown-menu-root'), id: state('dropdown-menu-root'),
ref: state<HTMLElement | null>(null), ref: state<HTMLElement | null>(null),
open: state(false), open: state(false),
modal: state(true),
dir: state<'ltr' | 'rtl'>('ltr'), dir: state<'ltr' | 'rtl'>('ltr'),
accessibleWhenDisabled: state(true), accessibleWhenDisabled: state(true),
onOpenChangeComplete: state<(open: boolean) => void>(() => undefined) onOpenChangeComplete: state<(open: boolean) => void>(() => undefined)

@ -49,7 +49,6 @@ interface MenuOpts
ProviderOpts, ProviderOpts,
StateProps<{ open: boolean }>, StateProps<{ open: boolean }>,
ActiveProps<{ ActiveProps<{
modal: boolean;
dir: Direction; dir: Direction;
accessibleWhenDisabled: boolean; accessibleWhenDisabled: boolean;
onOpenChangeComplete: OnChangeFn<boolean>; onOpenChangeComplete: OnChangeFn<boolean>;
@ -289,7 +288,8 @@ export class MenuContentProvider {
enabled: readableActive(() => this.provider.contentPresence.isPresent) enabled: readableActive(() => this.provider.contentPresence.isPresent)
}); });
// Dismissal — menus always close on outside click (unlike dialogs where modal=true ignores it) // Dismissal — menus always close on outside click. For modal/blocking
// semantics use Dialog/Drawer/AlertDialog instead.
const resolvedInteractBehavior = readableActive( const resolvedInteractBehavior = readableActive(
() => opts.interactOutsideBehavior.current ?? 'close' () => opts.interactOutsideBehavior.current ?? 'close'
); );

@ -20,8 +20,6 @@ export type MenuProps = {
onOpenChange?: OnChangeFn<boolean>; onOpenChange?: OnChangeFn<boolean>;
/** Callback after open/close animation completes. */ /** Callback after open/close animation completes. */
onOpenChangeComplete?: OnChangeFn<boolean>; onOpenChangeComplete?: OnChangeFn<boolean>;
/** Block outside interaction when open. @default true */
modal?: boolean;
/** Text direction for keyboard navigation. @default from Soma config or 'ltr' */ /** Text direction for keyboard navigation. @default from Soma config or 'ltr' */
dir?: Direction; dir?: Direction;
/** /**
@ -60,7 +58,7 @@ export type MenuContentProps = WithChild<
onEscapeKeydown?: (e: KeyboardEvent) => void; onEscapeKeydown?: (e: KeyboardEvent) => void;
/** @default 'close' */ escapeKeydownBehavior?: DismissalBehavior; /** @default 'close' */ escapeKeydownBehavior?: DismissalBehavior;
onInteractOutside?: (e: PointerEvent) => void; onInteractOutside?: (e: PointerEvent) => void;
/** Inherits from modal when not set. */ interactOutsideBehavior?: DismissalBehavior; /** @default 'close' */ interactOutsideBehavior?: DismissalBehavior;
onFocusOutside?: (e: FocusEvent) => void; onFocusOutside?: (e: FocusEvent) => void;
dir?: Direction; dir?: Direction;
style?: StyleProperties | string | null; style?: StyleProperties | string | null;

@ -222,7 +222,6 @@ export class MenubarMenuProvider {
id: readableActive(() => this.menuId.current), id: readableActive(() => this.menuId.current),
ref: state<HTMLElement | null>(null), ref: state<HTMLElement | null>(null),
open: boundOpen, open: boundOpen,
modal: readableActive(() => false),
dir: this.provider.opts.dir, dir: this.provider.opts.dir,
accessibleWhenDisabled: readableActive(() => true), accessibleWhenDisabled: readableActive(() => true),
onOpenChangeComplete: opts.onOpenChangeComplete onOpenChangeComplete: opts.onOpenChangeComplete

@ -12,7 +12,6 @@
// ── Live state ──────────────────────────────────────────────────────── // ── Live state ────────────────────────────────────────────────────────
// Soma · Provider // Soma · Provider
let open = $state(false); let open = $state(false);
let modal = $state(true);
let dir = $state<'ltr' | 'rtl'>('ltr'); let dir = $state<'ltr' | 'rtl'>('ltr');
let accessibleWhenDisabled = $state(true); let accessibleWhenDisabled = $state(true);
// Soma · Content // Soma · Content
@ -32,6 +31,14 @@
let theme = $state<'light' | 'dark' | 'system'>('system'); let theme = $state<'light' | 'dark' | 'system'>('system');
let viewLayers = $state<string[]>(['ruler']); let viewLayers = $state<string[]>(['ruler']);
let lastSelected = $state<string | null>(null); let lastSelected = $state<string | null>(null);
// Eidos · Trigger visual props (forwarded to <Button> under the hood).
// `variant` narrows ButtonVariant → ControlVariant (3) to stay
// consistent with the rest of the form-control surface.
let triggerVariant = $state<'surface' | 'outline' | 'ghost'>('surface');
let triggerSize = $state<'xs' | 'sm' | 'md' | 'lg' | 'xl'>('md');
let triggerIntent = $state<'neutral' | 'affirm' | 'fulfill' | 'risk' | 'threat' | 'loss'>(
'neutral'
);
// Composition // Composition
let showGroupHeading = $state(true); let showGroupHeading = $state(true);
let showSeparators = $state(true); let showSeparators = $state(true);
@ -99,7 +106,6 @@
'', '',
'<DropdownMenu.Provider', '<DropdownMenu.Provider',
' bind:open', ' bind:open',
!modal && ' modal={false}',
dir !== 'ltr' && ` dir="${dir}"`, dir !== 'ltr' && ` dir="${dir}"`,
!accessibleWhenDisabled && ' accessibleWhenDisabled={false}', !accessibleWhenDisabled && ' accessibleWhenDisabled={false}',
'>', '>',
@ -165,10 +171,9 @@
'', '',
'<DropdownMenu', '<DropdownMenu',
' bind:open', ' bind:open',
!modal && ' modal={false}',
dir !== 'ltr' && ` dir="${dir}"`, dir !== 'ltr' && ` dir="${dir}"`,
'>', '>',
` <DropdownMenu.Trigger>${triggerLabel}</DropdownMenu.Trigger>`, ` <DropdownMenu.Trigger${triggerVariant !== 'surface' ? ` variant="${triggerVariant}"` : ''}${triggerSize !== 'md' ? ` size="${triggerSize}"` : ''}${triggerIntent !== 'neutral' ? ` intent="${triggerIntent}"` : ''}>${triggerLabel}</DropdownMenu.Trigger>`,
` <DropdownMenu.Content${side !== 'bottom' ? ` side="${side}"` : ''}${align !== 'start' ? ` align="${align}"` : ''}>`, ` <DropdownMenu.Content${side !== 'bottom' ? ` side="${side}"` : ''}${align !== 'start' ? ` align="${align}"` : ''}>`,
showGroupHeading && ' <DropdownMenu.Group>', showGroupHeading && ' <DropdownMenu.Group>',
showGroupHeading && ' <DropdownMenu.GroupHeading>Account</DropdownMenu.GroupHeading>', showGroupHeading && ' <DropdownMenu.GroupHeading>Account</DropdownMenu.GroupHeading>',
@ -241,8 +246,14 @@
<!-- Live preview always rendered --> <!-- Live preview always rendered -->
<div data-uix-stage> <div data-uix-stage>
<div data-uix-stage-area bind:this={stageRef}> <div data-uix-stage-area bind:this={stageRef}>
<DropdownMenu bind:open {modal} {dir} {accessibleWhenDisabled}> <DropdownMenu bind:open {dir} {accessibleWhenDisabled}>
<DropdownMenu.Trigger>{triggerLabel}</DropdownMenu.Trigger> <DropdownMenu.Trigger
variant={triggerVariant}
size={triggerSize}
intent={triggerIntent}
>
{triggerLabel}
</DropdownMenu.Trigger>
<DropdownMenu.Content <DropdownMenu.Content
{side} {side}
{sideOffset} {sideOffset}
@ -369,13 +380,6 @@
<span data-uix-switch-label>{open ? 'open' : 'closed'}</span> <span data-uix-switch-label>{open ? 'open' : 'closed'}</span>
</span> </span>
</label> </label>
<label data-uix-control>
<span data-uix-control-label>modal <span data-uix-control-hint>block outside interaction</span></span>
<span data-uix-switch>
<input type="checkbox" bind:checked={modal} />
<span data-uix-switch-label>{modal ? 'on' : 'off'}</span>
</span>
</label>
<label data-uix-control> <label data-uix-control>
<span data-uix-control-label>accessibleWhenDisabled</span> <span data-uix-control-label>accessibleWhenDisabled</span>
<span data-uix-switch> <span data-uix-switch>
@ -538,6 +542,58 @@
</label> </label>
</div> </div>
<!-- ── Eidos · Trigger (visual chrome forwarded to <Button>) ─── -->
<div data-uix-subsection-head>
<span data-uix-layer-badge="eidos">eidos</span> Trigger
<span style="font-weight: normal; color: var(--uix-text-muted);">
· visual props forwarded to the underlying &lt;Button&gt;
</span>
</div>
<div data-uix-controls>
<label data-uix-control>
<span data-uix-control-label>variant</span>
<span data-uix-chips role="radiogroup">
{#each ['surface', 'outline', 'ghost'] as v (v)}
<button
data-uix-chip
data-active={triggerVariant === v}
onclick={() => (triggerVariant = v as typeof triggerVariant)}
>
{v}
</button>
{/each}
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>size</span>
<span data-uix-chips role="radiogroup">
{#each ['xs', 'sm', 'md', 'lg', 'xl'] as s (s)}
<button
data-uix-chip
data-active={triggerSize === s}
onclick={() => (triggerSize = s as typeof triggerSize)}
>
{s}
</button>
{/each}
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>intent</span>
<span data-uix-chips role="radiogroup">
{#each ['neutral', 'affirm', 'fulfill', 'risk', 'threat', 'loss'] as i (i)}
<button
data-uix-chip
data-active={triggerIntent === i}
onclick={() => (triggerIntent = i as typeof triggerIntent)}
>
{i}
</button>
{/each}
</span>
</label>
</div>
<!-- ── Bound state (mirrors) ───────────────────────────────────── --> <!-- ── Bound state (mirrors) ───────────────────────────────────── -->
<div data-uix-subsection-head> <div data-uix-subsection-head>
Bound state Bound state
@ -625,7 +681,6 @@
<tr><td class="name">id</td><td class="type">string</td><td class="default empty">auto</td><td>Stable identifier.</td></tr> <tr><td class="name">id</td><td class="type">string</td><td class="default empty">auto</td><td>Stable identifier.</td></tr>
<tr><td class="name">open <span data-uix-tag data-kind="bindable">bindable</span></td><td class="type">boolean</td><td class="default">false</td><td>Whether the menu is open.</td></tr> <tr><td class="name">open <span data-uix-tag data-kind="bindable">bindable</span></td><td class="type">boolean</td><td class="default">false</td><td>Whether the menu is open.</td></tr>
<tr><td class="name">onOpenChange / onOpenChangeComplete</td><td class="type">(open: boolean) =&gt; void</td><td class="default empty">—</td><td>Fire on every open/close and after the animation settles.</td></tr> <tr><td class="name">onOpenChange / onOpenChangeComplete</td><td class="type">(open: boolean) =&gt; void</td><td class="default empty">—</td><td>Fire on every open/close and after the animation settles.</td></tr>
<tr><td class="name">modal</td><td class="type">boolean</td><td class="default">true</td><td>Block outside interaction while open.</td></tr>
<tr><td class="name">dir</td><td class="type">'ltr' | 'rtl'</td><td class="default">'ltr'</td><td>Direction used for keyboard navigation inside sub-menus.</td></tr> <tr><td class="name">dir</td><td class="type">'ltr' | 'rtl'</td><td class="default">'ltr'</td><td>Direction used for keyboard navigation inside sub-menus.</td></tr>
<tr><td class="name">accessibleWhenDisabled</td><td class="type">boolean</td><td class="default">true</td><td>Disabled items remain focusable per WAI-ARIA APG; set false to skip them entirely.</td></tr> <tr><td class="name">accessibleWhenDisabled</td><td class="type">boolean</td><td class="default">true</td><td>Disabled items remain focusable per WAI-ARIA APG; set false to skip them entirely.</td></tr>
</tbody> </tbody>
@ -638,7 +693,13 @@
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead> <thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
<tbody> <tbody>
<tr><td class="name">id</td><td class="type">string</td><td class="default empty">auto</td><td>—</td></tr> <tr><td class="name">id</td><td class="type">string</td><td class="default empty">auto</td><td>—</td></tr>
<tr><td class="name">…native button attrs</td><td class="type">PrimitiveButtonAttributes</td><td class="default empty">—</td><td>Trigger renders a native <code>&lt;button&gt;</code>; all HTML button attrs flow through.</td></tr> <tr><td class="name">variant <span data-uix-tag data-kind="eidos">eidos</span></td><td class="type">ControlVariant: 'surface' | 'outline' | 'ghost'</td><td class="default">'surface'</td><td>Forwarded to the inner <code>&lt;Button&gt;</code>. Narrowed from ButtonVariant (per DEMO_AUTHORING_GUIDE §12.7) to stay consistent with form-control surface.</td></tr>
<tr><td class="name">size <span data-uix-tag data-kind="eidos">eidos</span></td><td class="type">'xs' | 'sm' | 'md' | 'lg' | 'xl'</td><td class="default">'md'</td><td>Button size scale.</td></tr>
<tr><td class="name">intent <span data-uix-tag data-kind="eidos">eidos</span></td><td class="type">'neutral' | 'affirm' | 'fulfill' | 'risk' | 'threat' | 'loss'</td><td class="default">'neutral'</td><td>Evaluative intent forwarded to Button — full canonical Intent set.</td></tr>
<tr><td class="name">color <span data-uix-tag data-kind="eidos">eidos</span></td><td class="type">ColorRole — see eidos/lib/types.ts</td><td class="default empty">—</td><td>Hierarchical color override (only applies under <code>intent='neutral'</code>).</td></tr>
<tr><td class="name">iconOnly · icon · endIcon · rounded <span data-uix-tag data-kind="eidos">eidos</span></td><td class="type">Button props</td><td class="default empty">—</td><td>Standard Button visual knobs forwarded through.</td></tr>
<tr><td class="name">child</td><td class="type">{`Snippet<[{ props }]>`}</td><td class="default empty">—</td><td>Opt out of <code>&lt;Button&gt;</code> entirely and render your own element.</td></tr>
<tr><td class="name">…native button attrs</td><td class="type">PrimitiveButtonAttributes</td><td class="default empty">—</td><td>All HTML button attrs flow through to the underlying button.</td></tr>
</tbody> </tbody>
</table> </table>
</div> </div>
@ -742,7 +803,6 @@
<tr><td>Group / GroupHeading</td><td>✓</td><td>✓</td><td>✓</td><td>✓</td></tr> <tr><td>Group / GroupHeading</td><td>✓</td><td>✓</td><td>✓</td><td>✓</td></tr>
<tr><td>Arrow part</td><td>✓</td><td>✓</td><td>—</td><td>✓</td></tr> <tr><td>Arrow part</td><td>✓</td><td>✓</td><td>—</td><td>✓</td></tr>
<tr><td>closeOnSelect per item</td><td>✓</td><td>✓</td><td>—</td><td>✓</td></tr> <tr><td>closeOnSelect per item</td><td>✓</td><td>✓</td><td>—</td><td>✓</td></tr>
<tr><td>modal mode</td><td>✓</td><td>✓</td><td>—</td><td>✓</td></tr>
<tr><td>accessibleWhenDisabled (APG)</td><td>—</td><td>—</td><td>—</td><td>✓</td></tr> <tr><td>accessibleWhenDisabled (APG)</td><td>—</td><td>—</td><td>—</td><td>✓</td></tr>
<tr><td>commit-select semantic event</td><td>—</td><td>—</td><td>—</td><td>✓</td></tr> <tr><td>commit-select semantic event</td><td>—</td><td>—</td><td>—</td><td>✓</td></tr>
</tbody> </tbody>

Loading…
Cancel
Save

Powered by TurnKey Linux.