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.vue | import { Spinner } from '@gxp-dev/uikit' |
@app/components/Header.vue | import { Header } from '@gxp-dev/uikit' |
@app/components/Countdown.vue | import { Countdown } from '@gxp-dev/uikit' |
@app/components/VideoPlayer.vue | import { VideoPlayer } from '@gxp-dev/uikit' |
@app/components/FileUploader.vue | import { FileUploader } from '@gxp-dev/uikit' |
@app/components/BarcodeScanner.vue | import { BarcodeScanner } from '@gxp-dev/uikit' |
@app/components/Leaderboard/Leaderboard.vue | import { Leaderboard } from '@gxp-dev/uikit' |
@app/components/Leaderboard/ActivityNotifications.vue | import { ActivityNotifications } from '@gxp-dev/uikit' |
@app/components/Leaderboard/AwardIcon.vue | import { AwardIcon } from '@gxp-dev/uikit' |
@app/components/AudioVisualizer.vue | import { 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.js | import { useMedia } from '@gxp-dev/uikit' |
@app/composables/scanning.js | import { useScanning } from '@gxp-dev/uikit' |
@app/composables/animations.js | import { useAnimations } from '@gxp-dev/uikit' |
@app/composables/errors.js | import { useErrors } from '@gxp-dev/uikit' |
@app/composables/nfcListener.js | import { 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.js | import { 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,TextareaCheckbox,RadioGroup,Switch,SliderSelect,Form,Calendar
Layout Components
Card,Separator,AspectRatio,ScrollAreaTabs,Accordion,CollapsibleSheet(slide-over panel)TablewithTableHeader,TableBody,TableRow,TableCell
Overlay Components
Dialog,AlertDialogPopover,Tooltip,HoverCardDropdownMenu,ContextMenu,Menubar
Feedback Components
Alertwith variants (default, destructive, warning)Badgewith variantsSkeleton(loading placeholders)Toaster(toast notifications via vue-sonner)Progress(progress bar)
Navigation
NavigationMenuPaginationCommand(command palette)
Display Components
AvatarwithAvatarImageandAvatarFallback
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/uikitpackage - 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 storybookto 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:
- Explore the Component Documentation to discover new features
- Review Theming Guide to customize the UI Kit for your brand
- Check Contributing Guide to add custom components
- Set up Storybook for component development
Welcome to @gxp-dev/uikit!