/** * Button — headless proprietary primitive. * * Client component (consumes `PermissionContext` + `AuditContext` via * `useContext`). See `docs/adr/primitives/001-button-rsc-default.md` for * the full trade-off analysis. * * Contracts honored: * - Permission gate (ARCHITECTURE §10). The button asks the ambient * policy `can('click', 'button', attrs?)`. Denied → the `mode` field * decides presentation (`'disabled'`, `'hidden'`, or `'readOnly'`). * - Audit event (§10). Every successful activation emits * `PRIMITIVE_ACTIONS.BUTTON_CLICK` with ISO-8601 timestamp. Denied * activations emit `DATA_ACTIONS.PERMISSION_DENIED` with the deny * reason so governance can index on them. */ import type { ButtonHTMLAttributes, ReactNode } from 'react'; import { type PermissionContext } from '../context.js'; type NativeButtonProps = Omit, 'type' | 'disabled' | 'aria-busy'>; export interface ButtonProps extends NativeButtonProps { /** * Button contents. Unstyled — wrap with icons / labels as needed. The * visual layer in `@oshon-ai/components` adds spacing and iconography * on top of this primitive. */ children: ReactNode; /** * Native `type` attribute. Defaults to `'button'` so the primitive * never accidentally submits a form. Pass `'submit'` explicitly for * form buttons. */ type?: 'button' | 'submit' | 'reset'; /** * Disable the button. Renders with `aria-disabled="true"` and the * native `disabled` attribute so screen readers and keyboard users * get consistent signal. When `disabled`, `onClick` does not fire and * no audit event is emitted. */ disabled?: boolean; /** * In-flight state. Sets `aria-busy="true"` + `disabled` so assistive * tech announces the button as unavailable while the action resolves. * The visual layer renders a spinner on top; the primitive itself does * not. */ loading?: boolean; /** * Per-instance permission override. Merges over the ambient * `PermissionContext`. Example: `permissions={{ mode: 'hidden' }}` * switches this one instance to render-as-null when denied. */ permissions?: Partial; /** * Resource name passed to `permissions.can(action, resource, attrs)`. * Default `'button'`. Consumers override for specific semantics, e.g. * `resource="row:edit"` for a row-level action button. */ resource?: string; /** * Attribute bag forwarded to `permissions.can(..., attrs)` for * attribute-based access control (row ID, tenant, owner). Primitives * never invent values here — the consumer supplies them. */ permissionAttrs?: Record; } /** * Headless button. Accessible `