npm package: @nosana/wallet-standard-vue
Description: Vue composables and components for wallet-standard
Latest version: 1.0.3

# @nosana/wallet-standard-vue

Vue composables for [wallet-standard](https://github.com/wallet-standard/wallet-standard), providing a generic way to integrate wallet functionality into Vue 3 applications.

This is a generic package that works with any wallet-standard compatible wallets.
For Solana-specific functionality, see [`@nosana/solana-vue`](https://www.npmjs.com/package/@nosana/solana-vue).

## Installation

```bash
npm install @nosana/wallet-standard-vue
```

**Dependencies:** `@wallet-standard/ui` and `@wallet-standard/ui-registry` are included and provide UI-friendly wallet representations (`UiWallet` and `UiWalletAccount`). The composables return these UI types instead of raw `Wallet` objects for better developer experience.

## Styling

The UI components (`WalletButton` and `WalletModal`) come with default styles that you can optionally import. If you prefer to style them yourself, you can skip importing the CSS.

### Import all styles

```javascript
import '@nosana/wallet-standard-vue/styles';
```

### Import individual component styles

```javascript
// Import only WalletButton styles
import '@nosana/wallet-standard-vue/styles/wallet-button';

// Import only WalletModal styles
import '@nosana/wallet-standard-vue/styles/wallet-modal';
```

### In your main entry file

```javascript
// main.js or main.ts
import { createApp } from 'vue';
import App from './App.vue';
import '@nosana/wallet-standard-vue/styles'; // Add this line

createApp(App).mount('#app');
```

### Customizing with CSS Variables

The default styles can be customized using CSS variables. You can override these variables in your own stylesheet to match your app's design.

#### Shared Variables

These variables are shared across all components and can be overridden globally:

```css
:root {
  /* Primary Colors */
  --wallet-primary: #3b82f6;
  --wallet-primary-hover: #2563eb;

  /* Text Colors */
  --wallet-text-primary: #111827;
  --wallet-text-secondary: #6b7280;

  /* Border Colors */
  --wallet-border: #e5e7eb;
  --wallet-border-hover: #d1d5db;

  /* Background Colors */
  --wallet-bg: white;
  --wallet-bg-gray: #f3f4f6;
  --wallet-bg-hover: #f9fafb;
  --wallet-bg-accent: #eff6ff;

  /* Typography */
  --wallet-font-family:
    -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
```

#### WalletButton Variables

```css
:root {
  /* Button Colors */
  --wallet-button-connect-bg: var(--wallet-primary);
  --wallet-button-connect-bg-hover: var(--wallet-primary-hover);
  --wallet-button-connect-text: var(--wallet-bg);
  --wallet-button-disconnect-bg: var(--wallet-bg);
  --wallet-button-disconnect-bg-hover: var(--wallet-bg-hover);
  --wallet-button-disconnect-text: var(--wallet-text-primary);
  --wallet-button-disconnect-border: var(--wallet-border);
  --wallet-button-disconnect-border-hover: var(--wallet-border-hover);

  /* Text Colors */
  --wallet-button-text-secondary: var(--wallet-text-secondary);

  /* Icon & Placeholder */
  --wallet-button-icon-placeholder-bg: var(--wallet-bg-gray);
  --wallet-button-icon-placeholder-text: var(--wallet-text-secondary);

  /* Spinner */
  --wallet-button-spinner-color: var(--wallet-button-connect-bg);

  /* Typography - can override shared variable */
  --wallet-button-font-family: var(--wallet-font-family);
}
```

#### WalletModal Variables

```css
:root {
  /* Modal Overlay */
  --wallet-modal-overlay-bg: rgba(0, 0, 0, 0.5);

  /* Modal Container */
  --wallet-modal-bg: var(--wallet-bg);
  --wallet-modal-border-radius: 12px;
  --wallet-modal-max-width: 400px;

  /* Modal Header */
  --wallet-modal-header-border: var(--wallet-border);
  --wallet-modal-title-color: var(--wallet-text-primary);

  /* Modal Close Button */
  --wallet-modal-close-color: var(--wallet-text-secondary);
  --wallet-modal-close-bg-hover: var(--wallet-bg-gray);
  --wallet-modal-close-border-radius: 0.375rem;

  /* Modal Content */
  --wallet-modal-empty-text: var(--wallet-text-secondary);

  /* Wallet Item */
  --wallet-item-bg: var(--wallet-bg);
  --wallet-item-border: var(--wallet-border);
  --wallet-item-border-hover: var(--wallet-primary);
  --wallet-item-bg-hover: var(--wallet-bg-accent);
  --wallet-item-border-radius: 0.5rem;

  /* Wallet Item Icon */
  --wallet-item-icon-bg: var(--wallet-bg-gray);
  --wallet-item-icon-placeholder-text: var(--wallet-text-secondary);

  /* Wallet Item Text */
  --wallet-item-name-color: var(--wallet-text-primary);

  /* Wallet Item Spinner */
  --wallet-item-spinner-color: var(--wallet-primary);

  /* Typography - can override shared variable */
  --wallet-modal-font-family: var(--wallet-font-family);
}
```

**Example - Customizing colors:**

```css
/* In your app's CSS file */
:root {
  /* Change the primary color globally */
  --wallet-primary: #8b5cf6;
  --wallet-primary-hover: #7c3aed;

  /* Or override specific button colors */
  --wallet-button-connect-bg: #10b981;
  --wallet-button-connect-bg-hover: #059669;
}
```

## Usage

### 1. Wrap your app with WalletProvider

```vue
<template>
  <WalletProvider>
    <App />
  </WalletProvider>
</template>

<script setup lang="ts">
import { WalletProvider } from '@nosana/wallet-standard-vue';
import App from './App.vue';
</script>
```

**WalletProvider Props:**

- `autoConnect` (optional, default: `false`): Automatically reconnect to the last connected wallet when the app loads. The wallet name is stored in localStorage and will be automatically reconnected on subsequent visits.

- `localStorageKey` (optional, default: `'walletName'`): The key used to store the last connected wallet name in localStorage. Useful if you want to use a custom key or have multiple instances.

**Example - With auto-connect enabled:**

```vue
<template>
  <WalletProvider :auto-connect="true">
    <App />
  </WalletProvider>
</template>

<script setup lang="ts">
import { WalletProvider } from '@nosana/wallet-standard-vue';
import App from './App.vue';
</script>
```

**Example - With custom localStorage key:**

```vue
<template>
  <WalletProvider auto-connect localStorage-key="myAppWallet">
    <App />
  </WalletProvider>
</template>

<script setup lang="ts">
import { WalletProvider } from '@nosana/wallet-standard-vue';
import App from './App.vue';
</script>
```

### 2. Use the composables in your components

#### useWallets

Get all available wallets:

```vue
<template>
  <div>
    <h2>Available Wallets</h2>
    <button v-for="wallet in wallets" :key="wallet.name" @click="connectWallet(wallet)">
      {{ wallet.name }}
    </button>
  </div>
</template>

<script setup lang="ts">
import { useWallets, useWallet } from '@nosana/wallet-standard-vue';

const { wallets } = useWallets();
const { connect } = useWallet();

const connectWallet = async (wallet: any) => {
  await connect(wallet);
};
</script>
```

#### useWallet

Manage wallet connection state:

```vue
<template>
  <div>
    <div v-if="!connected">
      <p>Not connected</p>
      <button
        v-for="wallet in wallets"
        :key="wallet.name"
        @click="connect(wallet)"
        :disabled="connecting"
      >
        Connect {{ wallet.name }}
      </button>
    </div>
    <div v-else>
      <p>Connected: {{ account?.address }}</p>
      <button @click="disconnect">Disconnect</button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { useWallets, useWallet } from '@nosana/wallet-standard-vue';

const { wallets } = useWallets();
const { wallet, account, accounts, connected, connecting, connect, disconnect } = useWallet();
</script>
```

## API Reference

### useWallets()

Returns all available wallets that support wallet-standard. Wallets are returned as `UiWallet` objects (from `@wallet-standard/ui-core`) which provide a UI-friendly representation with properties like `name`, `icon`, `chains`, and `accounts`.

**Returns:**

- `wallets`: A computed ref containing an array of `UiWallet` objects (access with `wallets.value`)

### useWallet()

Manages wallet connection state. Must be used within a `WalletProvider`.

**Returns:**

- `wallet`: A computed ref of the currently connected `UiWallet` (or `null`)
- `account`: A computed ref of the currently selected `UiWalletAccount` (or `null`)
- `accounts`: A computed ref of all `UiWalletAccount` objects from the connected wallet
- `connected`: A computed ref indicating if a wallet is connected
- `connecting`: A computed ref indicating if a connection is in progress
- `connectingWallet`: A computed ref of the `UiWallet` currently being connected
- `connect(wallet)`: Function to connect a wallet (accepts `UiWallet`)
- `disconnect()`: Function to disconnect the current wallet

## TypeScript Support

This package is written in TypeScript and includes type definitions.

## UI Components

### WalletButton

A ready-to-use button component that displays the wallet connection state and opens a modal to connect/disconnect wallets.

**Features:**

- Shows "Connect Wallet" when not connected
- Displays wallet name, icon, and address when connected
- Opens a modal to select and connect wallets
- Handles connection and disconnection states
- Highly customizable via props and slots

**Props:**

- `wallets` (optional): A list of wallets to display in the modal. If not provided, uses all available wallets.
- `connectText` (optional): Text shown on the connect button. Default: `"Connect Wallet"`
- `connectingText` (optional): Text shown when connecting. Use `{name}` as a placeholder. Default: `"Connecting to {name}..."`
- `disconnectIcon` (optional): Icon/text shown on the disconnect button. Default: `"×"`
- `addressChars` (optional): Number of characters to show at start/end of address. Default: `4`
- `modalTitle` (optional): Modal title. Default: `"Connect Wallet"`
- `modalEmptyMessage` (optional): Message when no wallets found. Default: `"No wallets found. Please install a wallet extension."`
- `modalShowCloseButton` (optional): Whether to show close button. Default: `true`
- `modalCloseButtonIcon` (optional): Close button icon. Default: `"×"`
- `modalCloseButtonLabel` (optional): Close button aria-label. Default: `"Close"`

**Slots:**

- `connect-content`: Customize the connect button content. Receives `{ connecting, connectingWallet }` as slot props.
- `connected-content`: Customize the connected button content. Receives `{ wallet, account, formattedAddress, disconnecting }` as slot props.
- `modal-title`: Customize the modal title.
- `modal-empty`: Customize the empty state message.
- `modal-wallet-item`: Customize each wallet item in the modal. Receives `{ wallet, connecting }` as slot props.
- `modal-close-icon`: Customize the close button icon.

**Example - Basic usage:**

```vue
<template>
  <WalletProvider>
    <WalletButton />
  </WalletProvider>
</template>

<script setup lang="ts">
import { WalletProvider, WalletButton } from '@nosana/wallet-standard-vue';
import '@nosana/wallet-standard-vue/styles';
</script>
```

**Example - With custom props:**

```vue
<template>
  <WalletProvider>
    <WalletButton
      connect-text="Connect Your Wallet"
      connecting-text="Connecting to {name}..."
      :address-chars="6"
      modal-title="Select Your Wallet"
    />
  </WalletProvider>
</template>

<script setup lang="ts">
import { WalletProvider, WalletButton } from '@nosana/wallet-standard-vue';
import '@nosana/wallet-standard-vue/styles';
</script>
```

**Example - With slots:**

```vue
<template>
  <WalletProvider>
    <WalletButton>
      <template #connect-content="{ connecting, connectingWallet }">
        <span v-if="connecting">🔄 {{ connectingWallet?.name }}</span>
        <span v-else>🔗 Connect</span>
      </template>

      <template #connected-content="{ wallet, formattedAddress }">
        <img :src="wallet?.icon" :alt="wallet?.name" />
        <span>{{ formattedAddress }}</span>
      </template>

      <template #modal-title>
        <h2>Choose Your Wallet</h2>
      </template>
    </WalletButton>
  </WalletProvider>
</template>

<script setup lang="ts">
import { WalletProvider, WalletButton } from '@nosana/wallet-standard-vue';
import '@nosana/wallet-standard-vue/styles';
</script>
```

### WalletModal

A modal component that displays available wallets and allows users to connect to them.

**Features:**

- Lists all available wallets with icons
- Shows wallet name and version
- Displays connection state and loading indicators
- Closes on wallet selection or backdrop click
- Highly customizable via props and slots

**Props:**

- `modelValue` (boolean): Controls the visibility of the modal
- `wallets` (optional): A list of wallets to display. If not provided, uses all available wallets from `useWallets()`.
- `title` (optional): Modal title text. Can also be customized using the `title` slot. Default: `"Connect Wallet"`
- `emptyMessage` (optional): Message shown when no wallets are available. Can also be customized using the `empty` slot. Default: `"No wallets found. Please install a wallet extension."`
- `showCloseButton` (optional): Whether to show the close button in the header. Default: `true`
- `closeButtonIcon` (optional): Close button icon/text. Can also be customized using the `close-icon` slot. Default: `"×"`
- `closeButtonLabel` (optional): Close button aria-label for accessibility. Default: `"Close"`

**Slots:**

- `title`: Customize the modal title.
- `empty`: Customize the empty state message.
- `wallet-item`: Customize each wallet item in the list. Receives `{ wallet, connecting }` as slot props.
- `close-icon`: Customize the close button icon.

**Events:**

- `update:modelValue`: Emitted when the modal should be closed

**Example - Basic usage:**

```vue
<template>
  <WalletProvider>
    <button @click="showModal = true">Open Wallet Modal</button>
    <WalletModal v-model="showModal" />
  </WalletProvider>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { WalletProvider, WalletModal } from '@nosana/wallet-standard-vue';
import '@nosana/wallet-standard-vue/styles';

const showModal = ref(false);
</script>
```

**Example - With custom props:**

```vue
<template>
  <WalletProvider>
    <button @click="showModal = true">Open Wallet Modal</button>
    <WalletModal
      v-model="showModal"
      title="Select Your Wallet"
      empty-message="No wallets detected. Please install a wallet extension."
      :show-close-button="true"
      close-button-icon="✕"
    />
  </WalletProvider>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { WalletProvider, WalletModal } from '@nosana/wallet-standard-vue';
import '@nosana/wallet-standard-vue/styles';

const showModal = ref(false);
</script>
```

**Example - With slots:**

```vue
<template>
  <WalletProvider>
    <button @click="showModal = true">Open Wallet Modal</button>
    <WalletModal v-model="showModal">
      <template #title>
        <h2>🔐 Choose Your Wallet</h2>
      </template>

      <template #empty>
        <div class="custom-empty">
          <p>No wallets found</p>
          <a href="https://phantom.app" target="_blank">Install Phantom</a>
        </div>
      </template>

      <template #wallet-item="{ wallet, connecting }">
        <div class="custom-wallet-item">
          <img :src="wallet.icon" :alt="wallet.name" />
          <span>{{ wallet.name }}</span>
          <span v-if="connecting">Connecting...</span>
        </div>
      </template>
    </WalletModal>
  </WalletProvider>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { WalletProvider, WalletModal } from '@nosana/wallet-standard-vue';
import '@nosana/wallet-standard-vue/styles';

const showModal = ref(false);
</script>
```

## License

MIT