# InstallButton

> The install call to action for an app; controlled through four states, with built-in English and Arabic labels, and a quiet "Open" once installed.

Source: https://docs.nasaqui.com/components/install-button

## Install

```bash
npx shadcn@latest add https://docs.nasaqui.com/r/install-button.json
```

A button that tracks an app through its lifecycle in a workspace: `available`, `installing`, `installed`, `update`.
It is controlled: your code owns `state` and moves it forward. Once installed it becomes a quiet "Open" button, so
an app the user already owns does not compete with apps still to install. It is built on
[`Button`](https://docs.nasaqui.com/components/button).

## When to use

- The install, get, open or update action on a store card, listing or app detail page.

## When not to use

- Any other action: use [`Button`](https://docs.nasaqui.com/components/button).
- Subscribing to a plan: use the call to action in [`PlanCard`](https://docs.nasaqui.com/components/plan-card).
- Long-running work outside install: use `Button` with `loading`.

## Import

```tsx
import { InstallButton, type InstallButtonProps, type InstallState } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"
```

## Quick start

```tsx
import { InstallButton, type InstallState } from "@fadymondy/nasaq/web";
import { useState } from "react";

export function InstallZekra() {
  const [state, setState] = useState<InstallState>("available");

  function install() {
    setState("installing");
    setTimeout(() => setState("installed"), 1500);
  }

  return <InstallButton appName="Zekra" state={state} onInstall={install} onOpen={() => {}} />;
}
```

## Anatomy

```
InstallButton           data-slot="install-button", data-state="<state>"   <Button>
├─ spinner              while state="installing" (Button loading)
├─ check icon           only when installed, aria-hidden
└─ label                Install | Get | Update | Open
```

## API

### `InstallButton`

`InstallButtonProps extends Omit<ButtonProps, "children" | "onClick" | "loading">`. Other `Button` props (such as
`disabled`, `className`, `type`) are forwarded.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `appName` | `string` | required | The app's name, appended to the accessible name: "Install Mahaam". |
| `state?` | `InstallState` | `"available"` | Where the app is in its lifecycle for this workspace. |
| `free?` | `boolean` | `false` | Free apps say "Get" instead of "Install". |
| `onInstall?` | `() => void` | none | Called on click when state is `available` or `installing`. |
| `onOpen?` | `() => void` | none | Called on click when state is `installed`. |
| `onUpdate?` | `() => void` | none | Called on click when state is `update`. |
| `labels?` | `Partial<{ install: string; get: string; open: string; update: string }>` | built-in | Override the English or Arabic labels. |
| `variant?` | `ButtonProps["variant"]` | `"secondary"` | Button variant. Ignored when installed (always `"ghost"`). |
| `size?` | `ButtonProps["size"]` | `"sm"` | Button size. |

### `InstallState`

`"available" | "installing" | "installed" | "update"`

| State | Renders | Click calls |
| --- | --- | --- |
| `available` | "Install" (or "Get" when `free`) | `onInstall` |
| `installing` | Spinner, disabled, `aria-busy` | none (disabled) |
| `installed` | Ghost "Open" with a check icon | `onOpen` |
| `update` | "Update" | `onUpdate` |

## Examples

### Free app

```tsx
import { InstallButton } from "@fadymondy/nasaq/web";

export function GetApp() {
  return <InstallButton appName="Moharrik" free onInstall={() => {}} />;
}
```

### Arabic with a custom label

```tsx
import { InstallButton, NasaqProvider } from "@fadymondy/nasaq/web";

export function ArabicInstall() {
  return (
    <NasaqProvider locale="ar" dir="rtl">
      <InstallButton appName="حوسبة" state="available" onInstall={() => {}} />
      <InstallButton appName="حوسبة" state="update" labels={{ update: "تحديث متاح" }} onUpdate={() => {}} />
    </NasaqProvider>
  );
}
```

## Accessibility

| Key | Action |
| --- | --- |
| `Enter` / `Space` | Activates the current action |
| `Tab` | Moves focus to and from the button |

- It is a native button. The accessible name is `"<label> <appName>"` ("Install Zekra"), so several install
  buttons on one page are distinguishable.
- While installing the button is disabled with `aria-busy`, but stays focusable so focus is not lost.
- The check icon is `aria-hidden`.
- Localise `appName`; the labels are built in for English and Arabic.

## RTL & i18n

- Built-in labels in English and Arabic (`تثبيت`, `احصل عليه`, `فتح`, `تحديث`), chosen by the provider locale
  (English without a provider). Override any with `labels`.
- Spinner and check icon sit on the inline start through the button's flex layout, so they mirror in RTL.

## Styling & tokens

- Inherits `Button` styling. The check icon uses `text-nq-success-text`.
- Target with `[data-slot=install-button]` or `[data-state=installed]`.
- Extend with `className`. Pass `variant="primary"` for the single main action on a page.

## Do / Don't

- **Do** own `state` in your code and move it as the install progresses.
- **Do** pass `appName` so the button has a distinct accessible name.
- **Do** use `free` for apps that cost nothing.
- **Don't** make an installed app's button loud; it stays a quiet "Open".
- **Don't** use more than one `primary` install button in a view.

## Related

- [Button](https://docs.nasaqui.com/components/button) · [ProductCard](https://docs.nasaqui.com/components/product-card) · [PlanCard](https://docs.nasaqui.com/components/plan-card) · [Price](https://docs.nasaqui.com/components/price)

## Lab

https://docs.nasaqui.com/?path=/docs/components-apps-platforms-install-button--docs

## Code

### React

```tsx
import { InstallButton, type InstallState } from "@fadymondy/nasaq/web";
import { useState } from "react";

export function InstallZekra() {
  const [state, setState] = useState<InstallState>("available");

  function install() {
    setState("installing");
    setTimeout(() => setState("installed"), 1500);
  }

  return <InstallButton appName="Zekra" state={state} onInstall={install} onOpen={() => {}} />;
}
```

### shadcn

```tsx
import { InstallButton, type InstallState } from "@/components/ui/install-button";
import { useState } from "react";

export function InstallZekra() {
  const [state, setState] = useState<InstallState>("available");

  function install() {
    setState("installing");
    setTimeout(() => setState("installed"), 1500);
  }

  return <InstallButton appName="Zekra" state={state} onInstall={install} onOpen={() => {}} />;
}
```

### Vue

```vue
<script setup lang="ts">
import { NqInstallButton, type InstallState } from "@fadymondy/nasaq/vue";
import { ref } from "vue";

const state = ref<InstallState>("available");
function install() {
  state.value = "installing";
  setTimeout(() => (state.value = "installed"), 1500);
}
</script>

<template>
  <NqInstallButton app-name="Mahaam" :state="state" @install="install" />
</template>
```

### Blade

```blade
<div class="flex items-center gap-3">
    <x-nq::install-button app-name="Mahaam" />
    <x-nq::install-button app-name="Mahaam" state="installing" />
    <x-nq::install-button app-name="Mahaam" state="installed" />
    <x-nq::install-button app-name="Mahaam" state="update" />
</div>
```

### HTML + Alpine

```html
<div class="flex items-center gap-3">
    <button data-slot="install-button"
     type="button"                         data-state="available" aria-label="Install Mahaam" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border-border bg-card text-foreground hover:bg-nq-hover h-control-sm px-2.5">
        Install</button>
    <button data-slot="install-button"
     type="button"          aria-busy="true"          disabled      data-disabled     data-state="installing" aria-label="Install Mahaam" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border-border bg-card text-foreground hover:bg-nq-hover h-control-sm px-2.5">
    <svg data-slot="spinner" aria-hidden="true" class="size-4 animate-spin motion-reduce:animate-none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M21 12a9 9 0 1 1-6.219-8.56"/>
</svg>    Install</button>
    <button data-slot="install-button"
     type="button"                         data-state="installed" aria-label="Open Mahaam" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover h-control-sm px-2.5">
        <svg aria-hidden="true" class="text-nq-success-text" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg>    Open</button>
    <button data-slot="install-button"
     type="button"                         data-state="update" aria-label="Update Mahaam" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border-border bg-card text-foreground hover:bg-nq-hover h-control-sm px-2.5">
        Update</button>
</div>
```
