Skip to main content
Version: v1 (Current)

Migration Guide: z-plugin-components to @gxp-dev/uikit

This guide helps developers migrate from the legacy z-plugin-components library to the new @gxp-dev/uikit package.

Overview

@gxp-dev/uikit is a complete replacement for z-plugin-components, built with modern standards:

  • TypeScript: Full type safety with IntelliSense support
  • shadcn-vue primitives: Battle-tested UI components built on reka-ui
  • Comprehensive testing: Every component has unit tests with Vitest
  • Tailwind CSS: Utility-first styling with CSS variables for theming
  • Storybook documentation: Interactive component explorer
  • Tree-shakeable: Only import what you need
  • Platform-agnostic: Decoupled from Eventfinity-specific APIs and stores

Installation

1. Install the package

pnpm add @gxp-dev/uikit

2. Import styles in your main entry file

// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'

// Import UI Kit styles
import '@gxp-dev/uikit/styles'

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

3. Configure Tailwind (if needed)

If your app uses Tailwind CSS, update your tailwind.config.js to include the UI Kit's content:

/** @type {import('tailwindcss').Config} */
export default {
content: [
'./index.html',
'./src/**/*.{vue,js,ts,jsx,tsx}',
'./node_modules/@gxp-dev/uikit/dist/**/*.{js,mjs}',
],
theme: {
extend: {},
},
plugins: [],
}

Component Migration

Domain Components

Old (z-plugin-components)New (@gxp-dev/uikit)
@app/components/Spinner.vueimport { Spinner } from '@gxp-dev/uikit'
@app/components/Header.vueimport { Header } from '@gxp-dev/uikit'
@app/components/Countdown.vueimport { Countdown } from '@gxp-dev/uikit'
@app/components/VideoPlayer.vueimport { VideoPlayer } from '@gxp-dev/uikit'
@app/components/FileUploader.vueimport { FileUploader } from '@gxp-dev/uikit'
@app/components/BarcodeScanner.vueimport { BarcodeScanner } from '@gxp-dev/uikit'
@app/components/Leaderboard/Leaderboard.vueimport { Leaderboard } from '@gxp-dev/uikit'
@app/components/Leaderboard/ActivityNotifications.vueimport { ActivityNotifications } from '@gxp-dev/uikit'
@app/components/Leaderboard/AwardIcon.vueimport { AwardIcon } from '@gxp-dev/uikit'
@app/components/AudioVisualizer.vueimport { AudioVisualizer } from '@gxp-dev/uikit'

Migration Example

Before:

<script setup>
import Spinner from '@app/components/Spinner.vue'
import Header from '@app/components/Header.vue'
</script>

<template>
<Header title="My Page" />
<Spinner size="lg" />
</template>

After:

<script setup lang="ts">
import { Spinner, Header } from '@gxp-dev/uikit'
</script>

<template>
<Header title="My Page" />
<Spinner size="lg" />
</template>

Composable Migration

Old (z-plugin-components)New (@gxp-dev/uikit)
@app/composables/media.jsimport { useMedia } from '@gxp-dev/uikit'
@app/composables/scanning.jsimport { useScanning } from '@gxp-dev/uikit'
@app/composables/animations.jsimport { useAnimations } from '@gxp-dev/uikit'
@app/composables/errors.jsimport { useErrors } from '@gxp-dev/uikit'
@app/composables/nfcListener.jsimport { useNfcListener } from '@gxp-dev/uikit'

Migration Example

Before:

import { useMedia } from '@app/composables/media'

const { startRecording, stopRecording } = useMedia()

After (with TypeScript):

import { useMedia, type UseMediaReturn } from '@gxp-dev/uikit'

const {
startRecording,
stopRecording,
stream,
devices,
error
}: UseMediaReturn = useMedia()

Utility Function Migration

Old (z-plugin-components)New (@gxp-dev/uikit)
@app/helpers/imageToBlob.jsimport { processUploadedImage, convertToCanvasBlob } from '@gxp-dev/uikit'

Before:

import { processUploadedImage } from '@app/helpers/imageToBlob'

After:

import { processUploadedImage } from '@gxp-dev/uikit'

New shadcn-vue Components

The UI Kit now includes 40+ professionally-designed primitives that were not available in z-plugin-components:

Form Components

  • Button, Input, Label, Textarea
  • Checkbox, RadioGroup, Switch, Slider
  • Select, Form, Calendar

Layout Components

  • Card, Separator, AspectRatio, ScrollArea
  • Tabs, Accordion, Collapsible
  • Sheet (slide-over panel)
  • Table with TableHeader, TableBody, TableRow, TableCell

Overlay Components

  • Dialog, AlertDialog
  • Popover, Tooltip, HoverCard
  • DropdownMenu, ContextMenu, Menubar

Feedback Components

  • Alert with variants (default, destructive, warning)
  • Badge with variants
  • Skeleton (loading placeholders)
  • Toaster (toast notifications via vue-sonner)
  • Progress (progress bar)
  • NavigationMenu
  • Pagination
  • Command (command palette)

Display Components

  • Avatar with AvatarImage and AvatarFallback

Example Usage

<script setup lang="ts">
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
CardFooter,
Button,
Input,
Label,
Alert,
AlertDescription,
Badge
} from '@gxp-dev/uikit'
</script>

<template>
<Card>
<CardHeader>
<CardTitle>Welcome</CardTitle>
<CardDescription>Get started with the new UI Kit</CardDescription>
</CardHeader>
<CardContent>
<Alert>
<AlertDescription>
All components are fully typed and tested
</AlertDescription>
</Alert>

<div class="space-y-4 mt-4">
<div>
<Label for="name">Name</Label>
<Input id="name" placeholder="Enter your name" />
</div>
<Badge>New</Badge>
</div>
</CardContent>
<CardFooter>
<Button>Submit</Button>
</CardFooter>
</Card>
</template>

Breaking Changes

1. Leaderboard Component

The Leaderboard component is now presentational only and no longer fetches data internally.

Before:

<template>
<!-- Component fetched data from API and managed sockets internally -->
<Leaderboard game-id="123" />
</template>

After:

<script setup lang="ts">
import { ref } from 'vue'
import { Leaderboard, type LeaderboardEntry } from '@gxp-dev/uikit'

// Parent component manages data fetching
const entries = ref<LeaderboardEntry[]>([])

async function loadLeaderboard() {
const response = await fetch('/api/leaderboard/123')
entries.value = await response.json()
}

loadLeaderboard()
</script>

<template>
<Leaderboard :entries="entries" />
</template>

Required Props:

interface LeaderboardEntry {
rank: number
name: string
score: number
avatar?: string
isCurrent?: boolean
}

2. AudioVisualizer Component

AudioVisualizer no longer imports useMedia internally. You must pass the stream explicitly.

Before:

<template>
<!-- Component used useMedia internally -->
<AudioVisualizer />
</template>

After:

<script setup lang="ts">
import { AudioVisualizer, useMedia } from '@gxp-dev/uikit'

const { stream } = useMedia()
</script>

<template>
<AudioVisualizer :stream="stream" />
</template>

3. FileUploader Events

Event payloads have been updated for consistency.

Before:

<template>
<FileUploader @file-selected="handleFile" />
</template>

<script setup>
function handleFile(file) {
console.log(file.name, file.size)
}
</script>

After:

<script setup lang="ts">
import { FileUploader, type FileSelectedPayload } from '@gxp-dev/uikit'

function handleFile(payload: FileSelectedPayload) {
console.log(payload.file.name, payload.file.size)
console.log(payload.preview) // Optional preview URL
}
</script>

<template>
<FileUploader @file-selected="handleFile" />
</template>

4. CSS Class Names

All components now use Tailwind utility classes instead of scoped CSS with custom properties.

Before:

/* Old component used custom CSS variables */
.spinner {
color: var(--spinner_color);
background: var(--spinner_background_color);
}

After:

<!-- New component uses Tailwind classes and CSS variables -->
<template>
<div class="animate-spin rounded-full border-2 border-primary">
<!-- Uses CSS variable --primary for theming -->
</div>
</template>

If you were overriding styles, you'll need to update your CSS:

Before:

.spinner {
--spinner_color: blue;
}

After:

/* Override CSS variable */
:root {
--primary: 220 100% 50%; /* HSL values for blue */
}

/* Or use Tailwind classes */
<Spinner class="text-blue-500" />

5. Platform Dependencies Removed

Components no longer depend on useGxpStore() or Eventfinity-specific APIs.

Before:

<script setup>
// Old component accessed global store internally
import Header from '@app/components/Header.vue'
</script>

<template>
<!-- Component read theme from useGxpStore() -->
<Header title="My Page" />
</template>

After:

<script setup lang="ts">
import { Header } from '@gxp-dev/uikit'

// Pass theme data explicitly if needed
const theme = {
primaryColor: '#3b82f6',
logo: '/logo.png'
}
</script>

<template>
<Header
title="My Page"
:logo="theme.logo"
:style="{ '--primary': theme.primaryColor }"
/>
</template>

TypeScript Benefits

All components and composables are fully typed:

import {
VideoPlayer,
type VideoPlayerProps,
useMedia,
type UseMediaReturn,
type MediaDevice
} from '@gxp-dev/uikit'

// Full IntelliSense support
const { devices, startRecording }: UseMediaReturn = useMedia()

// Type-safe props
const props: VideoPlayerProps = {
src: 'video.mp4',
autoplay: true,
controls: true
}

CSS Variables for Theming

The UI Kit uses CSS variables for theming, making it easy to customize:

/* src/styles/theme.css */
:root {
/* Primary colors */
--primary: 220 100% 50%;
--primary-foreground: 0 0% 100%;

/* Background colors */
--background: 0 0% 100%;
--foreground: 222.2 47.4% 11.2%;

/* Border colors */
--border: 214.3 31.8% 91.4%;

/* And many more... */
}

See the theming documentation for a complete list of CSS variables.

Common Pitfalls

1. Forgetting to Import Styles

If components look unstyled, make sure you've imported the styles:

// src/main.ts
import '@gxp-dev/uikit/styles'

2. Missing Type Imports

Import types explicitly for better TypeScript support:

// Good
import { Button, type ButtonVariants } from '@gxp-dev/uikit'

// Also works, but less explicit
import { Button } from '@gxp-dev/uikit'
import type { ButtonVariants } from '@gxp-dev/uikit'

3. Portal Components in Tests

When testing portal-based components (Dialog, Select, DropdownMenu), remember to:

import { mount } from '@vue/test-utils'
import { Dialog, DialogContent } from '@gxp-dev/uikit'

it('renders dialog content', async () => {
const wrapper = mount(Dialog, {
props: { defaultOpen: true },
attachTo: document.body, // Required for portals
slots: {
default: () => h(DialogContent, {}, 'Dialog content')
}
})

// Query from document.body, not wrapper
const content = document.body.querySelector('[role="dialog"]')
expect(content).toBeTruthy()

wrapper.unmount() // Clean up
})

4. Event Handler Signatures

Some events have new signatures. Check TypeScript types:

// Old
@file-selected="handleFile"

// New - check the payload type
import { type FileSelectedPayload } from '@gxp-dev/uikit'

function handleFile(payload: FileSelectedPayload) {
// payload.file, payload.preview
}

Migration Checklist

  • Install @gxp-dev/uikit package
  • Import styles in main.ts
  • Update component imports from local paths to @gxp-dev/uikit
  • Update composable imports from local paths to @gxp-dev/uikit
  • Replace custom form components with shadcn-vue primitives
  • Update Leaderboard to use prop-based data instead of internal fetching
  • Update AudioVisualizer to receive stream via prop
  • Update FileUploader event handlers for new payload structure
  • Update any custom CSS that depended on old class names
  • Add type annotations for TypeScript benefits
  • Remove platform-specific dependencies (useGxpStore, etc.)
  • Update tests for portal-based components
  • Test theming with CSS variables
  • Update documentation/comments

Getting Help

  • Storybook: Run pnpm storybook to explore components interactively
  • TypeScript: Hover over imports in VS Code for inline documentation
  • Tests: Check component test files for usage examples
  • Source Code: All components are in src/components/

For issues or questions, see the GitHub repository.

Next Steps

After migration:

  1. Explore the Component Documentation to discover new features
  2. Review Theming Guide to customize the UI Kit for your brand
  3. Check Contributing Guide to add custom components
  4. Set up Storybook for component development

Welcome to @gxp-dev/uikit!