npm package: @spearheadops/ui
Description: A Vue 3 component library extracted from the Spearhead project. It provides a set of pre-styled, accessible UI primitives intended for use by clients building custom forms and interfaces that integrate with Spearhead.
Latest version: 1.0.2

# @spearheadops/ui

A Vue 3 component library extracted from the Spearhead project. It provides a set of pre-styled, accessible UI primitives intended for use by clients building custom forms and interfaces that integrate with Spearhead.

## Installation

```bash
npm install @spearheadops/ui
```

This package requires Vue 3.5+ and Vue Router 4+ as peer dependencies.

## Setup

Import the stylesheet once in your application entry point:

```ts
import '@spearheadops/ui/style.css'
```

Then import components as needed:

```ts
import { SHButton, SHField, SHInput } from '@spearheadops/ui'
```

## Dark Mode

Components support dark mode via Vue's provide/inject system. Provide the `DarkModeKey` at the root of your app with a reactive boolean:

```ts
import { provide, ref } from 'vue'
import { DarkModeKey } from '@spearheadops/ui'

provide(DarkModeKey, ref(true)) // or false for light mode
```

## Components

### AppLink

A unified link component that renders as a `<router-link>` when given a `to` prop, or a plain `<a>` tag for external `href` links. Renders as a non-interactive `<span>` when `disabled`.

```vue
<AppLink :to="{ name: 'home' }">Go home</AppLink>
<AppLink href="https://example.com">External link</AppLink>
<AppLink disabled>Not clickable</AppLink>
```

**Props:** `to`, `href`, `disabled`, `active`, `block`, `prefix`, `activeClass`, `inactiveClass`, `exactActiveClass`

---

### SHAlert

A dismissible banner fixed to the top of the page. Supports an optional "Don't show again" checkbox and a countdown mode.

```vue
<SHAlert :visible="showBanner" @hide-alert="showBanner = false">
  <template #message>Your session will expire soon.</template>
</SHAlert>

<!-- With "don't show again" option -->
<SHAlert :visible="show" dont-show-again @hide-alert="() => (show = false)">
  <template #message>Welcome back!</template>
</SHAlert>
```

**Props:** `visible`, `dontShowAgain`, `countdown`
**Events:** `hide-alert(dontShowAgain?: boolean)`
**Slots:** `message`, `action`, `close`

---

### SHBadge

An inline label with a colored border, used to display status or category tags.

```vue
<SHBadge color="var(--color-primary)">Active</SHBadge>
<SHBadge color="var(--color-danger)" :reverse-color="true">Overdue</SHBadge>
```

**Props:** `color` (CSS color string, default `var(--color-surface-400)`), `reverseColor` (fills background with the color), `clickable`

---

### SHButton

A styled button with support for size variants, color themes, loading state, and optional router navigation.

```vue
<SHButton @click="save">Save</SHButton>
<SHButton color="danger" size="sm" @click="remove">Delete</SHButton>
<SHButton color="primary" :loading="isSaving">Saving…</SHButton>
<SHButton :to="{ name: 'dashboard' }">Go to Dashboard</SHButton>
```

**Props:**
- `color` — `ButtonTheme`: `primary`, `secondary`, `danger`, `success`, `warning`, `info`, or `surface-{0–900}` (default: `surface-200`)
- `size` — `sm` | `md` | `lg` (default: `md`)
- `disabled`, `loading`, `active`, `square`, `fullWidth`
- `to` — Vue Router `RouteLocationNamedRaw` for navigation
- `href` — URL for external navigation
- `innerClass` — class applied to the inner content span

---

### SHCard

A bordered, shadowed container for grouping content. Supports optional `header` and `footer` slots.

```vue
<SHCard>
  <template #header>Card Title</template>
  <p>Card content goes here.</p>
  <template #footer>Footer actions</template>
</SHCard>

<SHCard :archived="true">Archived item</SHCard>
<SHCard :no-border="true">Borderless card</SHCard>
```

**Props:** `archived` (dashed border style), `noBorder`, `noHover`
**Slots:** `header`, `default`, `footer`

---

### SHCheckBox

A checkbox with an optional label and built-in tooltip support. Binds via `v-model`.

```vue
<SHCheckBox v-model="isEnabled">Enable feature</SHCheckBox>
<SHCheckBox v-model="agreed" help="You must agree to continue.">I agree</SHCheckBox>
<SHCheckBox v-model="val" disabled />
```

**Props:** `id`, `disabled`, `help`, `alignRight`, `selectOnFocus`, `spellcheck`
**Model:** `boolean`

---

### SHDollarInput

A currency input that stores values in **cents** (integers) while displaying them in dollars. Extends `SHNumberInput`.

```vue
<SHDollarInput v-model="priceInCents" />
```

A model value of `1000` displays as `$10.00`. Emits the rounded cent value on change.

**Slots:** `prefix`, `suffix`
**Model:** `number | null`

---

### SHDropdown

A floating popup anchored to its trigger element, powered by Floating UI. Supports both click-to-toggle and hover modes.

```vue
<SHDropdown>
  <SHButton>Open menu</SHButton>
  <template #popup="{ close }">
    <ul>
      <li @click="close">Option 1</li>
      <li @click="close">Option 2</li>
    </ul>
  </template>
</SHDropdown>

<!-- Tooltip on hover -->
<SHDropdown open-on-hover>
  <span>Hover me</span>
  <template #popup>More info here.</template>
</SHDropdown>
```

**Props:** `disabled`, `placement` (default: `bottom-start`), `strategy`, `offset`, `flip`, `shift`, `autoPlacement`, `matchWidth`, `openOnHover`, `teleportToBody`
**Slots:** `default` (trigger), `popup` (receives `{ toggle, open, close, isOpen, isHovering }`)
**Exposed:** `open()`, `close()`, `isOpen`

---

### SHField

A form field wrapper that pairs a label with an input. Handles required state, validation highlighting, inline error messages, and help tooltips.

```vue
<SHField label="Email" required :requirement-satisfied="!!email">
  <SHInput v-model="email" type="email" />
</SHField>

<SHField label="Notes" block>
  <SHTextarea v-model="notes" />
</SHField>

<SHField label="Amount" :invalid="errors.amount">
  <SHDollarInput v-model="amount" />
</SHField>
```

**Props:** `label`, `required`, `requirementSatisfied`, `invalid` (string shows inline error message, boolean highlights in red), `disabled`, `hideLabel`, `help`, `block` (stacks label above input), `alignRight`
**Slots:** `default` (the input, receives `{ myId }`), `label`, `labelRight`, `help`

---

### SHInput

A styled text input with support for prefix/suffix content, clearable state, and a loading indicator.

```vue
<SHInput v-model="name" placeholder="Enter name" />
<SHInput v-model="query" clearable>
  <template #prefix>Search:</template>
</SHInput>
<SHInput v-model="url" type="url" :fetching="isLoading" />
```

**Props:** `id`, `type` (`text` | `number` | `time` | `email` | `tel` | `date` | `datetime-local` | `password`), `modelValue`, `placeholder`, `disabled`, `clearable`, `fetching`, `compact`, `alignRight`, `selectOnFocus`, `spellcheck`
**Slots:** `prefix`, `suffix`
**Exposed:** `inputEl`, `focus()`, `select()`

---

### SHNote

A colored callout block for contextual messages.

```vue
<SHNote theme="info">This is an informational note.</SHNote>
<SHNote theme="warning">Please review before continuing.</SHNote>
<SHNote theme="danger">This action cannot be undone.</SHNote>
```

**Props:** `theme` — `primary` | `secondary` | `info` | `success` | `warning` | `danger`

---

### SHNumberInput

A numeric input that displays a formatted value (via `Intl.NumberFormat`) when unfocused, and switches to a plain number input on focus. Useful for clean display of large numbers or decimals.

```vue
<SHNumberInput v-model="quantity" />
<SHNumberInput v-model="rate" :decimals="2" align-right />
```

**Props:** `modelValue` (`number | null`), `decimals`, `formatter` (`Intl.NumberFormat`), `alignRight`
**Slots:** `prefix`, `suffix`
**Exposed:** `focus()`, `select()`

---

### SHPill

A two-section pill badge with a filled left side and a light right side, useful for key/value labels.

```vue
<SHPill left="Status" right="Active" color="var(--color-success)" />
<SHPill color="var(--color-primary)" hover-color="var(--color-primary-up-200)">
  <template #left>Priority</template>
  <template #right>High</template>
</SHPill>
```

**Props:** `left`, `right`, `color` (default: `var(--color-primary)`), `hoverColor` (enables hover effect)

---

### SHSpinner

An animated loading spinner. Used internally by `SHButton` and `SHInput`, but available standalone.

```vue
<SHSpinner />
<SHSpinner size="sm" />
<SHSpinner size="xs" />
```

**Props:** `size` (default: `2x`)

---

### SHTextarea

A styled `<textarea>` with optional auto-resize and compact (underline-only) modes.

```vue
<SHTextarea v-model="notes" />
<SHTextarea v-model="bio" auto-resize />
<SHTextarea v-model="comment" compact />
```

**Props:** `compact`, `autoResize`
**Model:** `string | null | undefined`

---

### SHToggle

An on/off toggle switch. Binds via `v-model`.

```vue
<SHToggle v-model="isEnabled" />
<SHToggle v-model="setting" disabled />
```

**Props:** `disabled`
**Model:** `boolean | null | undefined`

---

### SHUndoConfirm

A pair of Cancel/Submit buttons for form footers. Integrates with the `DisableFormKey` injection to disable submission globally.

```vue
<SHUndoConfirm
  :confirm-enabled="isValid"
  :loading="isSaving"
  @undo="reset"
  @confirm="submit"
/>

<SHUndoConfirm
  confirm-text="Delete"
  confirm-color="danger"
  :confirm-enabled="true"
  @undo="cancel"
  @confirm="deleteItem"
/>
```

**Props:** `confirmEnabled`, `confirmColor` (`primary` | `danger` | `success`, default: `primary`), `confirmText` (default: `Submit`), `undoText` (default: `Cancel`), `hideUndo`, `loading`
**Events:** `undo`, `confirm`
**Slots:** `undo`, `confirm`

---

### SHValidationErrors

Displays a list of validation error messages with an animated striped warning background.

```vue
<SHValidationErrors :show="submitted" :errors="errors" />
```

When `errors` is empty, shows a success state with a default "No invalid fields." message (overridable via the default slot).

**Props:** `show` (boolean), `errors` (string[])
**Slots:** `default` (empty-state message), `extra`

---

## Utility Exports

### Formatters

```ts
import { centsToDollars, dollarFormatter, formatUSD } from '@spearheadops/ui'

formatUSD(1099)        // "$10.99"
centsToDollars(1099)   // 10.99
```

### Provider Keys

```ts
import { DarkModeKey } from '@spearheadops/ui'
// Provide a Ref<boolean> at the app root to propagate dark mode to all components
```

```ts
import { DisableFormKey } from '@spearheadops/ui'
// Provide a Ref<boolean> to disable all SHUndoConfirm submit buttons in the subtree
```