Examples

Shopping Cart

E-commerce cart with context sharing and lifecycle hooks.

A complete e-commerce shopping cart demonstration:

  1. Context sharing - Products data shared via desk context
  2. Lifecycle hooks - Track product additions/removals
  3. Dynamic calculations - Cart total updates automatically
  4. Quantity management - Update quantities from context

Laptop Pro

$1299.99

Wireless Mouse

$49.99

Mechanical Keyboard

$159.99

USB-C Hub

$79.99

Monitor 27"

$399.99

Webcam HD

$89.99
0 type(s)
0 item(s)

Your cart is empty

Project Structure

shopping-cart/
├── index.ts              # Shared types and injection key
├── ShoppingCart.vue       # Parent cart component
└── ProductCard.vue       # Individual product card

Shared Types (index.ts)

import type { InjectionKey, Ref } from 'vue';
import type { DeskCore } from '#vue-airport/composables/useCheckIn';

export interface CartItem {
  id: string;
  name: string;
  price: number;
  quantity: number;
  imageUrl?: string;
}

export interface CartContext {
  products: Ref<CartItem[]>;
  updateQuantity: (id: string, quantity: number) => void;
}

export const CART_DESK_KEY: InjectionKey<DeskCore<CartItem> & CartContext> = Symbol('cartDesk');

Parent Component (ShoppingCart.vue)

Creates the cart desk with context sharing:

<script setup lang="ts">
import { useCheckIn } from '#vue-airport/composables/useCheckIn';
import ProductCard from './ProductCard.vue';
import { type CartItem, type CartContext, CART_DESK_KEY } from '.';

// Available products catalog
const products = ref([
  {
    id: 'product-1',
    name: 'Laptop Pro',
    price: 1299.99,
    quantity: 1,
    imageUrl: 'i-heroicons-computer-desktop',
  },
  // ... more products
]);

// Create a desk for the shopping cart with context
const { createDesk } = useCheckIn<CartItem, CartContext>();
const { desk } = createDesk(CART_DESK_KEY, {
  devTools: true,
  debug: false,
  context: {
    products,
  },
  onCheckIn: (_id, data) => {
    console.log(`Product added to cart: ${data.name}`);
  },
  onCheckOut: (id) => {
    console.log(`Product removed from cart: ${id}`);
  },
});

// Computed properties for cart data
const cartItems = computed(() => desk.getAll());
const cartCount = computed(() => {
  return cartItems.value.reduce((total, item) => {
    return total + item.data.quantity;
  }, 0);
});
const cartTotal = computed(() => {
  return cartItems.value.reduce((total, item) => {
    return total + item.data.price * item.data.quantity;
  }, 0);
});

// Function to remove item from cart
const removeFromCart = (id: string | number) => {
  if (window.confirm('Remove this item from cart?')) {
    desk.checkOut(id);
  }
};

// Function to update cart item quantity
const updateCartQuantity = (id: string | number, newQty: number) => {
  desk.update(id, { quantity: newQty });
};

// Function to clear the entire cart
const clearCart = () => {
  if (window.confirm('Do you really want to empty the cart?')) {
    desk.clear();
  }
};

// Function to proceed to checkout
const checkout = () => {
  if (cartItems.value.length === 0) {
    // Should never happen due to button disable, but just in case
    window.alert('Your cart is empty!');
    return;
  }

  const orderSummary = cartItems.value
    .map(
      (item) =>
        `${item.data.name} x${item.data.quantity} - $${(item.data.price * item.data.quantity).toFixed(2)}`
    )
    .join('\n');

  window.alert(
    `Order confirmed!\n\nSummary:\n${orderSummary}\n\nTotal: $${cartTotal.value.toFixed(2)}`
  );
  desk.clear();
};
</script>

<template>
  <div>
    <div class="grid grid-cols-1 lg:grid-cols-[1fr_384px] gap-8 mt-6">
      <!-- Products section -->
      <div>
        <div class="grid grid-cols-2 gap-4">
          <ProductCard v-for="product in products" :id="product.id" :key="product.id" />
        </div>
      </div>
      <!-- Cart UI... -->
    </div>
  </div>
</template>

Child Component (ProductCard.vue)

Retrieves product data from desk context:

<script setup lang="ts">
import { useCheckIn } from '#vue-airport/composables/useCheckIn';
import { type CartItem, type CartContext, CART_DESK_KEY } from '.';
import { Button } from '@/components/ui/button';

/**
 * Product Card Component
 * Automatically checks in and retrieves product data from desk context.
 */

const props = defineProps<{
  id: string;
}>();

// Check in to the cart desk and get context
const { checkIn } = useCheckIn<CartItem, CartContext>();
const { desk } = checkIn(CART_DESK_KEY, {
  id: props.id,
  autoCheckIn: false,
  watchData: true,
  data: (desk) => {
    const product = desk.products.value.find((p) => p.id === props.id);
    return {
      id: props.id,
      name: product?.name ?? '',
      price: product?.price ?? 0,
      quantity: product?.quantity ?? 1,
      imageUrl: product?.imageUrl,
    };
  },
});

// Get product data from context
const productData = computed(() => {
  return desk?.products?.value.find((p) => p.id === props.id);
});

// Check if product is in the cart (checked in to desk)
const isInCart = computed(() => desk?.has(props.id) ?? false);

// Function to add product to cart
const addToCart = () => {
  if (!productData.value) return;

  // If already in cart, just increment quantity
  if (desk?.has(props.id)) {
    const currentItem = desk.get(props.id);
    if (currentItem) {
      const newQty = currentItem.data.quantity + 1;
      desk.update(props.id, { quantity: newQty });
      desk.updateQuantity(props.id, newQty);
    }
  } else {
    // Add new item to cart by checking in
    desk?.checkIn(props.id, {
      id: props.id,
      name: productData.value.name,
      price: productData.value.price,
      quantity: productData.value.quantity,
      imageUrl: productData.value.imageUrl,
    });
  }
};
</script>

<template>
  <div
    class="aspect-square border border-gray-200 dark:border-gray-700 rounded-lg p-3 bg-card flex flex-col gap-2 transition-all duration-200 hover:border-primary hover:shadow-md"
  >
    <div class="flex justify-center items-center h-24 text-6xl text-primary opacity-70">
      <UIcon :name="productData?.imageUrl || 'i-heroicons-cube'" />
    </div>

    <div class="flex flex-col gap-1 flex-1">
      <h4 class="text-sm font-semibold m-0 truncate">{{ productData?.name }}</h4>
      <div class="text-lg font-bold text-primary">${{ productData?.price.toFixed(2) }}</div>
    </div>

    <div class="flex flex-col gap-1.5">
      <!-- Add to Cart button when not in cart -->
      <Button v-if="!isInCart" size="sm" @click="addToCart">
        <UIcon name="i-heroicons-shopping-cart" class="w-4 h-4 mr-2" />
        Add to Cart
      </Button>
    </div>
  </div>
</template>

Key Concepts

Context Sharing

The desk shares data through context, similar to the Tabs example:

const { createDesk } = useCheckIn<CartItem, CartContext>();
const { desk } = createDesk(CART_DESK_KEY, {
  context: {
    products,
    updateQuantity,
  },
});

Child components access this context via the data function:

const { checkIn } = useCheckIn<CartItem, CartContext>();
const { desk } = checkIn(CART_DESK_KEY, {
  data: (desk) => {
    const product = desk.products.value.find((p) => p.id === props.id);
    return {
      id: props.id,
      name: product?.name ?? '',
      price: product?.price ?? 0,
      quantity: product?.quantity ?? 1,
      imageUrl: product?.imageUrl,
    };
  },
});

Lifecycle Hooks

Track cart operations with lifecycle hooks:

createDesk(CART_DESK_KEY, {
  onCheckIn: (id, data) => {
    console.log(`Product added: ${data.name}`);
  },
  onCheckOut: (id) => {
    console.log(`Product removed: ${id}`);
  },
});

Dynamic Calculations

Cart totals are computed from registry data:

const cartCount = computed(() => {
  return cartItems.value.reduce((total, item) => {
    return total + item.data.quantity;
  }, 0);
});

const cartTotal = computed(() => {
  return cartItems.value.reduce((total, item) => {
    return total + item.data.price * item.data.quantity;
  }, 0);
});

Quantity Management

Quantities are managed through the shared context:

// Update quantity in context
const updateCartQuantity = (id: string | number, newQty: number) => {
  desk.update(id, { quantity: newQty });
};

How It Works

  1. Parent provides context with products array and updateQuantity function
  2. Children retrieve data from context via data() function with watchData: true
  3. No props needed - only the product id is passed
  4. Quantity updates sync between context and cart items
  5. Cart calculations update automatically when items change