@pinooxhq/luma
v0.4.14
Published
Luma — A modern Vue 3 dashboard framework built on PrimeVue. Themeable, customizable, and ready for production.
Readme
@pinooxhq/luma
Vue 3 dashboard framework for Pinoox apps — routing, Pinia, auth, layouts, theming, and a PrimeVue 4 UI layer in one package.
Luma is more than a component kit. It is the front-end foundation of a Pinoox admin app: boot the shell with createApp(), drop in PageLayout, and ship pages with PrimeVue + Luma wrappers. Every layer (tokens, layouts, router, auth, components) can be replaced without forking.
Table of contents
- Requirements
- Install
- Vite plugin
- Quick start
createAppoptions- Direction (RTL / LTR)
- Customization
- Authentication
- Usage examples
- Components
- Subpath exports
- Upgrading
- License
Full prop / slot reference: docs/ui-reference.md · docs index: docs/README.md · page kit: docs/page-kit.md · entity forms: docs/guides/entity-form.md · release notes: CHANGELOG.md
Requirements
| Package | Version | Role |
|---------|---------|------|
| vue | ^3.5 | Runtime |
| primevue | ^4.5 | UI components |
| @primeuix/themes | ^2 | Aura-based theming (Luma Console preset) |
| pinia | ^4 | State |
| @vue/devtools-api | ^8 | Required by Pinia 4 |
| vue-router | ^5 | Routing |
| axios | ^1 | HTTP (used by auth / http) |
| lucide-vue-next | ^1 | Icons for <LIcon> |
| sass | ^1 | Compile @pinooxhq/luma/styles (devDependency) |
| @pinooxhq/auth | ^0.1 | Optional — bundled auth helpers |
| @pinooxhq/slug | ^0.1 | Persian → Finglish slugs (slugify, LSlugField) |
Node ≥ 18. Luma targets PrimeVue 4.x (latest 4.5 line) with @primeuix/themes 2.x.
Install
npm install @pinooxhq/lumaPeers (and Sass for styles):
npm install primevue@^4 @primeuix/themes@^2 pinia@^4 @vue/devtools-api@^8 vue-router axios lucide-vue-next
npm install -D sassOptional auth package (needed for useAuthStore / configureAuth flows):
npm install @pinooxhq/authWithout @pinooxhq/auth, createApp still boots and public pages work; requiresAuth routes redirect.
Import styles once in your app entry SCSS:
@use '@pinooxhq/luma/styles';Vite plugin
Use the official plugin so peer packages are deduped, path aliases are configured, performance is optimized, and file:-linked Luma hot-reloads correctly:
// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import luma from '@pinooxhq/luma/vite';
export default defineConfig({
plugins: [luma(), vue()],
});Or use createLumaViteConfig for a zero-boilerplate setup:
// vite.config.js
import vue from '@vitejs/plugin-vue';
import { createLumaViteConfig } from '@pinooxhq/luma/vite';
export default createLumaViteConfig({
plugins: [vue()],
});What luma() does:
- Dedupes & pre-bundles peers (
primevue,pinia,vue-router,yup, …) against the consumernode_modules— avoids duplicate inject keys and optimizes cold-start dev speed. - Auto-detects entry & warmup — resolves theme entry from
frontend.config.php,index.html, orsrc/main.jsand configures server warmup. - App aliases — automatically registers standard Pinoox theme aliases (
@/,@views,@pages,@stores,@components,@composables,@config,@utils,@api,@assets,@layouts). - Vendor chunk splitting — splits production builds into clean vendor chunks (
vendor-vue,vendor-luma,vendor-prime,vendor-tiptap,vendor-icons,vendor-date,vendor-http). - Config & Environment — automatically reads
luma.config.jsand loads.env/.env.localfiles. - Diagnostics (
lumaDoctor) — checks consumernode_modulesduringvite devand warns if required dependencies or local checkout paths are missing. - Allows the Luma package root through
server.fs.allowand enables watch polling forfile:/ symlink installs. - Optional local checkout — set
LUMA_LOCAL=/path/to/luma-uiorluma({ local: '/path/to/luma-ui' }). Aliases every@pinooxhq/lumaentry (JS, Sass, Vazir fonts) to that tree. Leave unset to use npm.
Typography: Luma’s default stack is Vazir ($px-font-sans + @font-face in styles, and DEFAULT_THEME_CONFIG.font). Override with applyThemeConfig({ font: { sans, mono } }) when needed.
Plugin options
luma({
perf: true, // Dev pre-bundling & build chunk splitting (default: true)
entry: 'src/main.js', // Custom entry file (or auto-detected)
warmup: ['src/main.js'], // Dev server warmup files
configFile: 'luma.config.js', // Theme config file (or set appConfig: false)
doctor: true, // Run dependency health-checks in dev (default: true)
dedupe: ['my-shared-lib'], // Additional packages to dedupe
alias: { '@custom': 'src/custom' }, // Custom aliases
extraOptimizeDeps: ['my-lib'], // Additional optimizeDeps
extraChunkGroups: [], // Custom Rollup/Rolldown vendor chunk groups
fsAllow: ['/custom/peer-dir'], // Additional server.fs.allow paths
watchPolling: false, // Polling options for file links
local: '/path/to/luma', // Local Luma source override
})Local ↔ npm switch
# .env.local (app) — use checkout
LUMA_LOCAL={PATH_TO_LOCAL_LUMA}/luma-ui
# unset / comment out — use published @pinooxhq/luma from node_modulesPrefer loading the checkout’s vite.js when LUMA_LOCAL is set so plugin fixes apply before publish:
import { defineConfig, loadEnv } from 'vite';
import { pathToFileURL } from 'node:url';
import path from 'node:path';
export default defineConfig(async ({ mode }) => {
const env = loadEnv(mode, process.cwd(), '');
const local = (env.LUMA_LOCAL || '').trim();
const { default: luma } = local
? await import(pathToFileURL(path.join(path.resolve(local), 'vite.js')).href)
: await import('@pinooxhq/luma/vite');
return {
plugins: [luma(local ? { local } : {})],
};
});Quick start
Minimal layout:
your-app/
├── package.json
├── vite.config.js
├── theme.config.js ← visual tokens (colors, fonts, layout)
└── src/
├── main.js ← createApp(...)
└── config/
├── theme.js ← brand, nav, pageMeta
└── routes.jstheme.config.js — visual tokens
Written to :root as CSS variables at boot (no Sass rebuild to rebrand):
export default {
brand: {
primary: '#0E73FD',
primaryHover: '#0858D4',
primaryActive: '#0644A8',
primarySoft: '#DBE7FE',
bgLight: '#F4F4FE',
bgDark: '#060912',
},
font: {
sans: 'Vazir, Vazirmatn, Inter, system-ui, sans-serif',
mono: 'JetBrains Mono, monospace',
},
layout: {
sidebarWidth: '280px',
sidebarCollapsedWidth: '76px',
topbarHeight: '64px',
pageMaxWidth: '1280px',
radius: 'lg',
},
// Optional:
// direction: 'rtl',
// auth: { endpoints: { me, login, logout }, skipMe, autoLoginFromUrl },
};src/config/theme.js — brand, nav, page meta
import logo from '@/assets/images/brand.svg';
export const themeConfig = {
brand: {
title: 'Acme Shop',
subtitle: 'Sales admin',
logo,
},
nav: {
sections: [
{
key: 'overview',
label: 'Overview', // static heading; set collapsible: true for accordion
items: [
{ key: 'dashboard', label: 'Dashboard', icon: 'layout-dashboard', route: 'shop.dashboard' },
{ key: 'orders', label: 'Orders', icon: 'shopping-cart', route: 'shop.orders' },
{
key: 'catalog',
label: 'Catalog',
icon: 'layers',
children: [
{ key: 'categories', label: 'Categories', icon: 'folder-tree', route: 'shop.products.categories' },
{ key: 'brands', label: 'Brands', icon: 'award', route: 'shop.products.brands' },
],
},
],
},
],
},
pageMeta: {
'shop.dashboard': { title: 'Dashboard', lead: 'Sales overview', badge: 'Live' },
'shop.orders': { title: 'Orders', lead: 'Open and completed', badge: 'Sales' },
},
user: { roleLabel: 'Store manager' },
};
export default themeConfig;src/config/routes.js
import { PageLayout } from '@pinooxhq/luma/layouts';
export const routes = [
{
path: '/',
component: PageLayout,
meta: { requiresAuth: true },
children: [
{
path: '',
name: 'shop.dashboard',
component: () => import('@pages/dashboard/page-dashboard.vue'),
},
{
path: 'orders',
name: 'shop.orders',
component: () => import('@pages/orders/page-orders.vue'),
},
],
},
];src/main.js
import { createApp } from '@pinooxhq/luma';
import { RootShell } from '@pinooxhq/luma/layouts';
import pinia from '@stores/index.js';
import themeConfig from '../theme.config.js';
import { themeConfig as appThemeConfig } from '@/config/theme.js';
import { routes } from '@/config/routes.js';
createApp({
AppRoot: RootShell,
themeConfig: {
...appThemeConfig,
brand: { ...appThemeConfig.brand, ...themeConfig.brand },
font: themeConfig.font,
layout: themeConfig.layout,
direction: themeConfig.direction,
auth: themeConfig.auth,
},
routes,
pinia,
});In pages:
import { usePage } from '@pinooxhq/luma';
const { pageTitle, pageLead, pageBadge } = usePage();createApp options
| Option | Required | Description |
|--------|----------|-------------|
| AppRoot | yes | Top-level Vue component — usually RootShell from @pinooxhq/luma/layouts |
| themeConfig | no | Brand, nav, pageMeta, tokens, direction, auth |
| routes | no | Vue Router route table (default []) |
| pinia | no | Pinia instance; installed and set active for guards |
| mount | no | Mount selector (default #app) |
| auth | no | Options passed to configureAuth() (overrides themeConfig.auth) |
| verifyAuth | no | Async hook — full control over session checks (skips built-in me()) |
| IconComponent | no | Global <LIcon> registration (e.g. pass Luma’s LIcon) |
AppRoot is required so the Node-safe root barrel never pulls .vue SFCs into smoke tests / tooling.
Direction (RTL / LTR)
createApp resolves direction and syncs <html dir> + PrimeVue rtl:
themeConfig.direction('rtl'|'ltr')- Existing
<html dir>/<body dir> window.__PINOOX__.direction(boot payload /VITE_DIRECTION)- Fallback:
'ltr'
Teleported overlays (Select, DatePicker, menus, confirm) inherit direction via Luma SCSS — no per-app overlay CSS required for the common cases.
// theme.config.js
export default {
direction: 'rtl',
// …
};Customization
Visual tokens
| Group | Keys |
|-------|------|
| brand | primary, primaryHover, primaryActive, primarySoft, bgLight, bgDark |
| font | sans, mono |
| layout | sidebarWidth, sidebarCollapsedWidth, topbarHeight, pageMaxWidth, radius |
createApp calls applyThemeConfig() for you. You can also call it at runtime (white-label / multi-tenant):
import { applyThemeConfig, useTheme } from '@pinooxhq/luma';
applyThemeConfig({ brand: { primary: '#10b981' } });
const { toggleTheme, isDark } = useTheme();Sass overrides
@use '@pinooxhq/luma/styles';
:root {
--px-primary-soft: rgba(14, 115, 253, 0.08);
}
[data-theme='dark'] {
--px-bg: #0a0a14;
}themeConfig shape
{
brand: { title, subtitle?, logo?, primary?, /* visual brand keys */ },
nav: { sections: Array<{ key, label, items: Array<{ key, label, icon, route?, children? }>, collapsible?, defaultCollapsed? }> },
pageMeta: Record<string, { title, lead?, badge? }>,
user?: { roleLabel? },
font?: { sans?, mono? },
layout?: { sidebarWidth?, sidebarCollapsedWidth?, topbarHeight?, pageMaxWidth?, radius? },
direction?: 'rtl' | 'ltr',
auth?: { endpoints?, skipMe?, autoLoginFromUrl? },
loading?: { enabled?, delay?, exclude?, label? },
}Omitted fields fall back to Luma defaults. Nav section label is a static heading; set collapsible: true for accordion sections. Nav items may include children for a nested submenu.
Layouts
Typical pattern: RootShell as AppRoot, PageLayout as the authenticated route layout (sidebar, topbar, mobile nav, drawer).
import { RootShell, PageLayout } from '@pinooxhq/luma/layouts';
import { LSidebar, LTopbar, LMobileNav } from '@pinooxhq/luma/ds';
import { LView, LHeader, LIcon } from '@pinooxhq/luma/ui';Compose DS / UI pieces into a custom layout when PageLayout is too opinionated — RootShell only provides the outer wrapper, global Toast, confirm dialog, HTTP loading overlay, and theme CSS variables.
Global HTTP loading
RootShell mounts <LLoading />. Any request through Luma http is counted; the overlay appears after delay ms so fast calls do not flash.
// theme.config.js
export default {
loading: {
enabled: true,
delay: 220,
exclude: ['/pricing/fx/quotes'],
label: 'Loading',
},
};http.get('/pricing/fx/quotes', { skipLoading: true });configureHttpLoading() updates the same options at runtime. { skipLoading: true } always wins for that request.
While a local loading surface is active (table skeleton, custom spinner), the overlay stays hidden even if HTTP is in flight. LDataTable :loading and useTableRows() register automatically. Anywhere else:
import { useLocalLoading } from '@pinooxhq/luma';
useLocalLoading(loading); // ref or computedTable skeleton
Keep the table visible while the first fetch runs. Pass :loading to LDataTable and wrap cells with LColumnBody:
<LDataTable :value="rows" :loading="loading" paginator data-key="id">
<Column field="name" header="Name">
<template #body="{ data }">
<LColumnBody :data="data" part="entity">
{{ data.name }}
</LColumnBody>
</template>
</Column>
<Column header="">
<template #body="{ data }">
<LColumnBody :data="data" part="actions" :skel-props="{ count: 2 }">
…
</LColumnBody>
</template>
</Column>
</LDataTable>For a custom PrimeVue DataTable, use useTableRows(items, loading) and LTableSkel / isSkelRow().
Authentication
Defaults match Pinoox account APIs. Override only what you need.
Default guard flow
On first navigation, authGuard:
- Optionally adopts
?__manager_token=…whenthemeConfig.auth.autoLoginFromUrl: true - Calls
auth.me()once (remotestrategy →/account/api/v1/auth/{login,logout,get}by default) - Redirects to
/account/loginwhenrequiresAuthcannot proceed
Custom endpoints
// theme.config.js
auth: {
endpoints: {
me: '/api/v1/auth/me',
login: '/api/v1/auth/login',
logout: '/api/v1/auth/logout',
},
},Replace the strategy
import { createApp, configureAuth } from '@pinooxhq/luma';
import { RootShell } from '@pinooxhq/luma/layouts';
configureAuth({
endpoints: { me: '/api/auth/me', login: '/api/auth/login' },
});
createApp({ AppRoot: RootShell, themeConfig, routes, pinia });Skip me()
auth: { skipMe: true, autoLoginFromUrl: true },Trusts token presence and skips the HTTP round-trip.
Full control — verifyAuth
createApp({
AppRoot: RootShell,
themeConfig,
routes,
pinia,
verifyAuth: async ({ store, route, adoptedFromUrl }) => {
if (!store.token) return false;
const profile = await fetchMyProfile();
store.user = profile;
return profile.active === true;
},
});| Return | Effect |
|--------|--------|
| true | Allow navigation |
| false / throw | Redirect to login |
Helpers
import { auth, useAuthStore, http, configureAuth, getActiveAuth } from '@pinooxhq/luma';http is an axios client with auth headers wired — prefer it over a fresh axios instance.
Theme-context panels mount under __PINOOX__.url.BASE (/panel) and set auth.loginUrl to the browser path (/panel/auth/login). Vue Router still uses unprefixed records (/auth/login). Do not rewrite url.BASE in the app. Use:
import {
resolveHistoryBase,
bindAuthRedirect,
resolveGuestExit,
} from '@pinooxhq/luma';
const router = createRouter({
history: createWebHistory(resolveHistoryBase()),
routes,
});
bindAuthRedirect(router);Usage examples
DataTable page
<script setup>
import { ref } from 'vue';
import DataTable from 'primevue/datatable';
import Column from 'primevue/column';
import Tag from 'primevue/tag';
import Button from 'primevue/button';
import InputText from 'primevue/inputtext';
import IconField from 'primevue/iconfield';
import InputIcon from 'primevue/inputicon';
import { usePage } from '@pinooxhq/luma';
import { LView, LHeader, LCard } from '@pinooxhq/luma/ui';
const { pageTitle, pageLead } = usePage();
const orders = ref([
{ id: 1001, customer: 'Alex Rivera', total: 1250, status: 'paid' },
{ id: 1002, customer: 'Sam Chen', total: 890, status: 'pending' },
]);
const filters = ref({ global: '' });
const statusSeverity = { paid: 'success', pending: 'warn', shipped: 'info' };
</script>
<template>
<LView>
<LHeader :title="pageTitle" :lead="pageLead">
<Button label="New order" severity="primary" />
</LHeader>
<LCard>
<IconField>
<InputIcon class="pi pi-search" />
<InputText v-model="filters.global" placeholder="Search…" />
</IconField>
<DataTable
:value="orders"
v-model:filters="filters"
:global-filter-fields="['customer', 'status']"
paginator
:rows="10"
striped-rows
>
<Column field="id" header="ID" sortable />
<Column field="customer" header="Customer" sortable />
<Column field="status" header="Status">
<template #body="{ data }">
<Tag :value="data.status" :severity="statusSeverity[data.status]" />
</template>
</Column>
</DataTable>
</LCard>
</LView>
</template>Docs: primevue.org/datatable
Form + LField
For entity CRUD with Yup defaults / toForm / toPayload, see Entity Form (index: docs/README.md).
<script setup>
import { reactive, ref } from 'vue';
import InputText from 'primevue/inputtext';
import Select from 'primevue/select';
import Button from 'primevue/button';
import { http } from '@pinooxhq/luma';
import { LView, LHeader, LCard, LField } from '@pinooxhq/luma/ui';
const form = reactive({ name: '', category: null });
const errors = ref({});
const submitting = ref(false);
const categories = [
{ label: 'Apparel', value: 'apparel' },
{ label: 'Shoes', value: 'shoes' },
];
async function submit() {
submitting.value = true;
errors.value = {};
try {
await http.post('/api/v1/products', form);
} catch (err) {
errors.value = err.response?.data?.errors ?? {};
} finally {
submitting.value = false;
}
}
</script>
<template>
<LView>
<LHeader title="New product" lead="Add an item to the catalog" />
<LCard>
<form class="product-form" @submit.prevent="submit">
<LField label="Name" :error="errors.name?.[0]">
<InputText v-model="form.name" :invalid="!!errors.name" />
</LField>
<LField label="Category">
<Select
v-model="form.category"
:options="categories"
option-label="label"
option-value="value"
placeholder="Select…"
/>
</LField>
<Button type="submit" label="Save" :loading="submitting" />
</form>
</LCard>
</LView>
</template>Toast / Dialog
Global Toast is mounted by RootShell. Use useToast() anywhere:
<script setup>
import Button from 'primevue/button';
import Dialog from 'primevue/dialog';
import { useToast } from 'primevue/usetoast';
import { ref } from 'vue';
const toast = useToast();
const open = ref(false);
function notify() {
toast.add({
severity: 'success',
summary: 'Saved',
detail: 'Changes were stored.',
life: 3000,
});
}
</script>
<template>
<Button label="Notify" @click="notify" />
<Button label="Delete" severity="danger" @click="open = true" />
<Dialog v-model:visible="open" header="Confirm" modal>
<p>Delete this item?</p>
<template #footer>
<Button label="Cancel" text @click="open = false" />
<Button label="Delete" severity="danger" @click="open = false" />
</template>
</Dialog>
</template>Dark mode
<script setup>
import { useTheme } from '@pinooxhq/luma';
import { LThemeToggle } from '@pinooxhq/luma/ds';
const { isDark, toggleTheme } = useTheme();
</script>
<template>
<LThemeToggle :model-value="isDark" @update:model-value="toggleTheme" />
</template>Components
Rule of thumb: use PrimeVue directly for forms, tables, overlays. Reach for an L* wrapper when you want Luma’s themed chrome on top.
Luma UI (@pinooxhq/luma/ui)
| Component | Purpose |
|-----------|---------|
| LView / LPage | Page shells with consistent padding |
| LHeader | Classic page header (title, lead, badge, actions slot) |
| LPageHeader | Modern header (eyebrow, title, lead, icon) |
| LPageToolbar | Sticky page actions |
| LPageContainer | Honors pageMaxWidth + gutters |
| LPanel | Content panel (incl. flush table chrome) |
| LCard | Themed card (PrimeVue Card) |
| LStatCard | KPI / metric card |
| LEmptyPanel | Empty state + CTA |
| LBadge | Status tag |
| LButton | Variant / severity / size / shape system |
| LField | Label, hint, error + input slot |
| LDatePicker | Themed date picker |
| LTabs | Tab strip helpers |
| LToolbar | Filter / action row |
| LSpinner | Loading spinner |
| LToast | In-page toast host (global Toast still in RootShell) |
| LConfirmDialog | Confirm surface with RTL-aware layout |
| LRichEditor | TipTap rich text |
| LIcon | Lucide wrapper (<LIcon name="shopping-cart" />) |
Design-system chrome (@pinooxhq/luma/ds)
| Component | Purpose |
|-----------|---------|
| LSidebar | Nav sidebar (PageLayout) |
| LTopbar | Top bar |
| LMobileNav | Mobile bottom nav |
| LThemeToggle | Light / dark control |
| LEmptyState | Deprecated alias of LEmptyPanel |
PrimeVue 4 (wired via Console preset)
Import any core component in pages — preset, ripple, RTL, and dark mode are already installed by setupPrimeVue:
| Area | Components |
|------|------------|
| Form | InputText, Textarea, Password, InputMask, InputNumber, InputOtp, Select, MultiSelect, AutoComplete, Checkbox, RadioButton, ToggleSwitch, SelectButton, Slider, Rating, DatePicker, … |
| Data | DataTable, DataView, Tree, TreeTable, Timeline, Paginator |
| Panels | Card, Panel, Accordion, Tabs, Stepper, Toolbar |
| Overlay | Dialog, Drawer, Popover, Tooltip, ConfirmDialog, ConfirmPopup |
| Feedback | Toast, Message, Tag, Badge, Chip, ProgressBar, ProgressSpinner, Skeleton |
| Menu | Menu, Menubar, MegaMenu, PanelMenu, TieredMenu, Breadcrumb |
| Media | Image, Carousel, FileUpload |
| Misc | Avatar, Divider, Splitter, ScrollPanel, Knob, Button, ToggleButton |
See primevue.org for the full API. Theme CSS comes from @primeuix/themes via Luma’s preset — you do not need a separate PrimeVue CSS import for the Console look.
Subpath exports
| Path | Contents |
|------|----------|
| @pinooxhq/luma | Node-safe barrel (createApp, composables, auth, theme helpers — no .vue SFCs) |
| @pinooxhq/luma/ui | UI primitives listed above |
| @pinooxhq/luma/ds | Shell chrome + legacy LEmptyState |
| @pinooxhq/luma/layouts | RootShell, PageLayout |
| @pinooxhq/luma/composables | usePage, … |
| @pinooxhq/luma/router | createAppRouter, authGuard, bindAuthRedirect, redirectToLogin, route prefetching, BASE/login helpers |
| @pinooxhq/luma/core | auth, http, useAuthStore, configureAuth, env, dates, media helpers |
| @pinooxhq/luma/plugins | setupPrimeVue, ConsolePreset |
| @pinooxhq/luma/styles | Main SCSS bundle |
| @pinooxhq/luma/tokens | SCSS design tokens |
| @pinooxhq/luma/fonts | Vazir registration |
| @pinooxhq/luma/theme-config | Nav / pageMeta / theme resolution helpers |
| @pinooxhq/luma/createApp | Factory only |
| @pinooxhq/luma/vite | Vite plugin, presets, and context loader |
| @pinooxhq/luma/vite/perf | Vite pre-bundling and vendor chunk splitting presets |
| @pinooxhq/luma/vite/config | createLumaViteConfig, createAppAliases, lumaDoctor, config loaders |
| @pinooxhq/luma/vite/theme-auto | Automatic theme entry detection and icon aliases |
| @pinooxhq/luma/preset | Console preset (default export) |
Upgrading
npm update @pinooxhq/lumaSemantic versioning (see CHANGELOG.md):
- Patch — internal fixes, no public API break
- Minor — new components / helpers, backwards compatible
- Major — breaking changes, documented in the changelog
Pin to ^0.4.0 (or stricter) for predictable upgrades.
Migrating from 0.3.x → 0.4.x
| Package | 0.3.x | 0.4.x |
|---------|-------|-------|
| primevue | ^4 | ^4.5 |
| @primeuix/themes | ^1 | ^2 |
| pinia | ^3 | ^4 |
| @vue/devtools-api | — | ^8 |
- Install the peer set from Install.
- Prefer
LEmptyPaneloverLEmptyState. - Prefer the
luma()Vite plugin when developing against afile:link. - Set
themeConfig.directionwhen you need explicit RTL.
License
MIT © Pinoox
