diff --git a/docs/features/admin-ui-modernization.md b/docs/features/admin-ui-modernization.md index 9855411..b4db30f 100644 --- a/docs/features/admin-ui-modernization.md +++ b/docs/features/admin-ui-modernization.md @@ -15,7 +15,7 @@ priority: high # Admin UI Modernization > [!abstract] Summary -> Migrate the admin dashboard from **React 18 + MUI** to **Vue 3** for consistency with the machine UI, using PrimeVue for components and tRPC for type-safe API communication. +> Migrate the admin dashboard from **React 18 + MUI** to **Vue 3** for consistency with the machine UI, using **shadcn-vue** for components and tRPC for type-safe API communication. ## Quick Links @@ -118,9 +118,9 @@ packages/admin-ui/ │ │ └── SettingsPage.vue ││ │ └─────────────────────────────────────────────────────┘│ │ ┌─────────────────────────────────────────────────────┐│ -│ │ UI Components (PrimeVue) ││ -│ │ ├── DataTable, Charts, Forms ││ -│ │ └── Custom components ││ +│ │ UI Components (shadcn-vue + Radix Vue) ││ +│ │ ├── Button, Card, Dialog, Table... ││ +│ │ └── Custom: CryptoAmount, StatusBadge... ││ │ └─────────────────────────────────────────────────────┘│ │ ┌─────────────────────────────────────────────────────┐│ │ │ State (Pinia) ││ @@ -241,147 +241,224 @@ packages/admin-ui/ ## UI Component Library -### PrimeVue vs Alternatives +### shadcn-vue vs Alternatives -| Library | Bundle | Components | Vue 3 | Tailwind | -|---------|--------|------------|-------|----------| -| **PrimeVue** | Tree-shakeable | 90+ | Yes | Yes | -| Naive UI | ~80kb | 80+ | Yes | Manual | -| Vuetify 3 | ~300kb | 80+ | Yes | No | -| Element Plus | ~200kb | 70+ | Yes | No | -| Headless UI | ~10kb | 10 | Yes | Yes | +| Library | Approach | Bundle | Tailwind | Control | +|---------|----------|--------|----------|---------| +| **shadcn-vue** | Copy to repo | Zero overhead | Native | Full | +| PrimeVue | npm install | Tree-shake | Adapter | Theme only | +| Vuetify 3 | npm install | ~300kb | No | Theme only | +| Headless UI | npm install | ~10kb | Yes | Style only | +| Radix Vue | npm install | ~15kb | Yes | Full | -> [!decision] PrimeVue -> - **Tree-shakeable** - Only import what you use -> - **Tailwind integration** - PrimeVue + Tailwind preset -> - **Rich data table** - Critical for admin dashboards -> - **Chart components** - Built-in Chart.js integration -> - **Accessibility** - WCAG compliant -> - **Unstyled mode** - Full control with Tailwind +> [!decision] shadcn-vue +> - **Copy, don't install** - Components live in your codebase +> - **Tailwind-native** - No style conflicts or overrides +> - **Radix Vue primitives** - Accessible, unstyled foundation +> - **Full control** - Modify any component directly +> - **No library updates** - You own the code +> - **Consistent** - Same approach for admin + machine UI -### PrimeVue Setup +### shadcn-vue Setup + +```bash +# Initialize shadcn-vue +npx shadcn-vue@latest init + +# Add components as needed +npx shadcn-vue@latest add button +npx shadcn-vue@latest add card +npx shadcn-vue@latest add data-table +npx shadcn-vue@latest add dialog +npx shadcn-vue@latest add dropdown-menu +npx shadcn-vue@latest add input +npx shadcn-vue@latest add toast +``` ```typescript // src/main.ts import { createApp } from 'vue' -import PrimeVue from 'primevue/config' -import Aura from '@primeuix/themes/aura' -import ToastService from 'primevue/toastservice' -import ConfirmationService from 'primevue/confirmationservice' - import App from './App.vue' import router from './router' import { pinia } from './stores' +import './styles/globals.css' // Tailwind + shadcn styles + const app = createApp(App) -app.use(PrimeVue, { - theme: { - preset: Aura, - options: { - darkModeSelector: '.dark', - cssLayer: { - name: 'primevue', - order: 'tailwind-base, primevue, tailwind-utilities', - }, - }, - }, -}) - -app.use(ToastService) -app.use(ConfirmationService) app.use(router) app.use(pinia) app.mount('#app') ``` +### Component Structure + +``` +src/components/ +├── ui/ # shadcn-vue components (copied) +│ ├── button/ +│ │ ├── Button.vue +│ │ └── index.ts +│ ├── card/ +│ │ ├── Card.vue +│ │ ├── CardHeader.vue +│ │ ├── CardContent.vue +│ │ └── index.ts +│ ├── data-table/ +│ │ ├── DataTable.vue +│ │ ├── DataTablePagination.vue +│ │ └── index.ts +│ ├── dialog/ +│ ├── dropdown-menu/ +│ ├── input/ +│ ├── toast/ +│ └── ... +└── custom/ # Your custom components + ├── CryptoAmount.vue + ├── FiatAmount.vue + └── StatusBadge.vue +``` + ### Component Examples -#### Data Table Wrapper +#### Data Table with TanStack Table ```vue - - ``` @@ -391,76 +468,58 @@ const selectedRows = ref([]) ```vue ``` -#primevue #components +#shadcn #components --- @@ -594,6 +653,14 @@ import { ref } from 'vue' import { useRouter } from 'vue-router' import { useAuthStore } from '@/stores/auth' import { startAuthentication } from '@simplewebauthn/browser' +import { trpc } from '@/api/client' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card' +import { Alert, AlertDescription } from '@/components/ui/alert' +import { Separator } from '@/components/ui/separator' +import { KeyRound, Lock, Loader2 } from 'lucide-vue-next' const router = useRouter() const auth = useAuthStore() @@ -632,52 +699,49 @@ async function handlePasskeyLogin() { @@ -821,15 +885,12 @@ Migrate pages in order of complexity: import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import { resolve } from 'path' -import Components from 'unplugin-vue-components/vite' -import { PrimeVueResolver } from '@primevue/auto-import-resolver' +import tailwindcss from '@tailwindcss/vite' export default defineConfig({ plugins: [ vue(), - Components({ - resolvers: [PrimeVueResolver()], - }), + tailwindcss(), ], resolve: { @@ -846,7 +907,6 @@ export default defineConfig({ output: { manualChunks: { vue: ['vue', 'vue-router', 'pinia'], - primevue: ['primevue'], charts: ['chart.js', 'vue-chartjs'], }, }, @@ -889,13 +949,16 @@ export default defineConfig({ "vue": "^3.5.0", "vue-router": "^4.4.0", "pinia": "^2.2.0", - "@primevue/themes": "^4.2.0", - "primevue": "^4.2.0", - "primeicons": "^7.0.0", + "radix-vue": "^1.9.0", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.0", + "tailwind-merge": "^2.5.0", + "@tanstack/vue-table": "^8.20.0", "@trpc/client": "^11.0.0", "@tanstack/vue-query": "^5.60.0", "@vueuse/core": "^11.0.0", "@simplewebauthn/browser": "^10.0.0", + "lucide-vue-next": "^0.460.0", "chart.js": "^4.4.0", "vue-chartjs": "^5.3.0" }, @@ -910,9 +973,7 @@ export default defineConfig({ "tailwindcss": "^4.0.0", "autoprefixer": "^10.4.0", "eslint": "^9.14.0", - "eslint-plugin-vue": "^9.30.0", - "unplugin-vue-components": "^0.27.0", - "@primevue/auto-import-resolver": "^4.2.0" + "eslint-plugin-vue": "^9.30.0" } } ``` @@ -926,7 +987,7 @@ export default defineConfig({ | Aspect | Before (React) | After (Vue 3) | |--------|---------------|---------------| | Framework | React 18 | Vue 3 | -| UI Library | MUI (~300kb) | PrimeVue (tree-shake) | +| UI Library | MUI (~300kb) | shadcn-vue (own code) | | State | Zustand | Pinia | | API | Apollo GraphQL | tRPC | | Styling | Tailwind | Tailwind (keep) | @@ -939,11 +1000,11 @@ export default defineConfig({ | Chunk | Before | After | |-------|--------|-------| | Framework | ~42kb | ~33kb | -| UI Library | ~300kb | ~80kb* | +| UI Library | ~300kb | ~15kb* | | API Client | ~50kb | ~5kb | -| **Total** | **~400kb** | **~120kb** | +| **Total** | **~400kb** | **~60kb** | -*PrimeVue with tree-shaking, only used components +*shadcn-vue components are copied to your codebase, only includes what you use #comparison #summary diff --git a/docs/features/machine-ui-modernization.md b/docs/features/machine-ui-modernization.md index 582e146..e49603c 100644 --- a/docs/features/machine-ui-modernization.md +++ b/docs/features/machine-ui-modernization.md @@ -102,6 +102,7 @@ var websocket = null - **Pinia** - Type-safe state management - **VueUse** - Composables for common tasks - **Vue I18n** - Internationalization + - **shadcn-vue** - Shared components with admin UI > [!note] Why Not Svelte? > Svelte has the smallest bundle, but Vue has: @@ -109,6 +110,9 @@ var websocket = null > - More developers familiar with it > - Better tooling maturity +> [!tip] Shared UI Components +> Use shadcn-vue for base components (Button, Card, etc.) to share code between machine and admin UIs via `@lamassu/ui-shared` package. + #vue #framework --- diff --git a/docs/modernization-plan.md b/docs/modernization-plan.md index 5dbf58c..6d4d5f9 100644 --- a/docs/modernization-plan.md +++ b/docs/modernization-plan.md @@ -42,7 +42,7 @@ graph TB end subgraph "Admin UI" - vue_admin[Vue 3 + PrimeVue] + vue_admin[Vue 3 + shadcn-vue] tailwind[Tailwind CSS] end @@ -161,10 +161,10 @@ graph LR | Aspect | Before (React) | After (Vue 3) | |--------|---------------|---------------| | Framework | React 18 | Vue 3 | -| UI Library | MUI (~300kb) | PrimeVue (tree-shake) | +| UI Library | MUI (~300kb) | shadcn-vue (~15kb) | | State | Zustand | Pinia | | API | Apollo GraphQL | tRPC | -| Bundle | ~400kb | ~120kb | +| Bundle | ~400kb | ~60kb | **Benefits:** - Single framework across all UIs