Three RFC drift corrections discovered during F3 implementation: 1. AD-5 icon rendering: corrected from "<i :class='i-tabler-X'>" utility-class pattern (which would require UnoCSS, not installed) to "@iconify/vue's <Icon> component with name='tabler-X' prop" (existing Crewli pattern producing real SVG output). The thin wrapper shipped in F3 B6 as apps/app/src/components/Icon.vue accordingly. 2. AD-2 theme architecture: corrected package reference from @primevue/themes@^4.5 (deprecated by PrimeFaces) to @primeuix/themes@^2 (the path now prescribed by PrimeVue 4's official install docs at primevue.org/vite/). Same maintainers, same API surface (definePreset, Aura preset, semantic tokens). F3 commit B1 already uses the corrected package. 3. Appendix B Aura theme token plan: updated import-path examples to @primeuix/themes and @primeuix/themes/aura accordingly. Also updated: - §6 F3 deliverables list: dependency line now reads @primeuix/themes@^2 with a footnote linking to the B1 rationale. - Appendix C Version Pinning Policy: separated @primeuix/themes from the primevue/^primevue/forms lockstep pin (independent release cadence). - dev-docs/PRIMEVUE_COMPONENTS.md §3 (Data display): VIcon row updated to <Icon name="tabler-..." />; surrounding migration-spirit paragraph rewritten; §10 external-resources link relabeled to @primeuix/themes. These are RFC drift corrections — the implementation in F3 (commits B1, B2, B6 of this sprint) already uses the corrected packages and import paths. This commit aligns the spec with reality so future contributors don't reach for the deprecated/inaccurate documentation. .claude-sync/ regenerates automatically post-commit via lefthook. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
390 lines
26 KiB
Markdown
390 lines
26 KiB
Markdown
# PRIMEVUE_COMPONENTS — PrimeVue Component Conventions for Crewli
|
||
|
||
> Authoritative reference for PrimeVue component selection, theming, forms,
|
||
> and DataTable conventions in the Crewli SPA. Read this before adding or
|
||
> migrating a UI component during F4 of [RFC-WS-FRONTEND-PRIMEVUE](./RFC-WS-FRONTEND-PRIMEVUE.md).
|
||
> This document encodes Crewli-specific conventions; for component
|
||
> reference (props, slots, events) cross-reference https://primevue.org/.
|
||
|
||
**Status:** Foundation (F2). Refined incrementally by F4 sub-packages as
|
||
real migration experience surfaces gaps.
|
||
**Aligned to:** PrimeVue 4.5.x with the Aura preset and `@primevue/forms`.
|
||
PrimeVue is installed in F3; F2 documents intent only.
|
||
**Replaces:** [`VUEXY_COMPONENTS.md`](./VUEXY_COMPONENTS.md) (now a
|
||
deprecation stub; deletion in F6).
|
||
|
||
---
|
||
|
||
## 1. Purpose and scope
|
||
|
||
This document defines:
|
||
|
||
1. Which PrimeVue component (or Tailwind / native equivalent) replaces each Vuetify component used in the SPA today
|
||
2. The Aura theme tokens that carry Crewli's brand (teal primary, dark mode)
|
||
3. The canonical form pattern (`@primevue/forms` + Zod resolver + `<FormField>` wrapper)
|
||
4. The canonical DataTable pattern (lazy / virtual / column-template conventions)
|
||
5. When to use the `pt` (pass-through) API, Tailwind utilities, or Aura tokens for customization
|
||
6. How to navigate the migration phase where Vuetify and PrimeVue both ship in the SPA simultaneously (F4a–F4d)
|
||
|
||
Out of scope here:
|
||
|
||
- Dependency installation steps (see [F3](./RFC-WS-FRONTEND-PRIMEVUE.md#f3--foundation-2-days))
|
||
- Per-component props/slot reference (see https://primevue.org/)
|
||
- The full 75-component Vuetify usage inventory (see [MIGRATION-AUDIT-PRIMEVUE.md §1](./MIGRATION-AUDIT-PRIMEVUE.md))
|
||
|
||
---
|
||
|
||
## 2. When to read this document
|
||
|
||
| Goal | Read |
|
||
|---|---|
|
||
| Picking a PrimeVue equivalent for a Vuetify usage during F4 | §3 |
|
||
| Customizing Crewli's brand colors / dark mode | §4 |
|
||
| Writing or migrating a form | §5 |
|
||
| Migrating a `<VDataTable>` or `<VDataTableServer>` | §6 |
|
||
| Deciding between `pt`, Tailwind utility, or Aura token override | §7, §8 |
|
||
| Working on a not-yet-migrated surface during F4 | §9 |
|
||
| Understanding the test-runtime story during migration | [ARCH-TESTING.md §7](./ARCH-TESTING.md) |
|
||
|
||
Cross-document relations:
|
||
|
||
- [RFC-WS-FRONTEND-PRIMEVUE.md](./RFC-WS-FRONTEND-PRIMEVUE.md) — full migration plan, sprint breakdown, architectural decisions (AD-1 through AD-12), risk register
|
||
- [MIGRATION-AUDIT-PRIMEVUE.md](./MIGRATION-AUDIT-PRIMEVUE.md) — Vuetify usage inventory (§1), `@core` / `@layouts` surface area (§2), form-layer inventory (§4), DataTable inventory (§6)
|
||
- [VUEXY_COMPONENTS.md](./VUEXY_COMPONENTS.md) — deprecation stub; its pre-F2 content is reachable via git history (commit `1c449ff6204cae6371da08c34ea8934d6b2ffcb8`) for reference on un-migrated surfaces
|
||
- [ARCH-TESTING.md](./ARCH-TESTING.md) §7 — explains why Vuetify lives in test infrastructure during the migration
|
||
|
||
---
|
||
|
||
## 3. Component mapping by category
|
||
|
||
The Crewli SPA uses ~73 distinct Vuetify components today (52 with ≥3 uses,
|
||
20 long-tail with <3 uses). The tables below group them into six functional
|
||
categories. Each category is followed by one paragraph on the migration
|
||
spirit — direct prop translation is rarely possible, since PrimeVue's
|
||
customization model is `pt` + Tailwind, not Vuetify's deep-customization
|
||
slots.
|
||
|
||
For component-level prop and slot reference, follow the linked PrimeVue
|
||
docs URL — this document does not duplicate them.
|
||
|
||
### 3.1 Form inputs
|
||
|
||
| Vuetify | PrimeVue | Reference | Notes |
|
||
|---|---|---|---|
|
||
| `VTextField` | `InputText` | https://primevue.org/inputtext/ | `density='compact'` → `size='small'`; wrapped via `<FormField>` for label + error |
|
||
| `VTextarea` | `Textarea` | https://primevue.org/textarea/ | Auto-resize via `autoResize` prop |
|
||
| `VSelect` | `Select` | https://primevue.org/select/ | Renamed from `Dropdown` in v4; `:items` → `:options`, `item-title` → `optionLabel`, `item-value` → `optionValue` |
|
||
| `VAutocomplete` | `AutoComplete` | https://primevue.org/autocomplete/ | Server-side: bind `@complete` instead of `:items` |
|
||
| `VCombobox` | `AutoComplete` with `:multiple` | https://primevue.org/autocomplete/ | Free-text + dropdown; PrimeVue uses one component for both |
|
||
| `VCheckbox` | `Checkbox` | https://primevue.org/checkbox/ | `binary` prop for single-value checkboxes |
|
||
| `VRadio` / `VRadioGroup` | `RadioButton` | https://primevue.org/radiobutton/ | No group component; bind `v-model` directly to each `RadioButton` |
|
||
| `VSwitch` | `ToggleSwitch` | https://primevue.org/toggleswitch/ | Renamed from `InputSwitch` in v4 |
|
||
| `VOtpInput` | `InputOtp` | https://primevue.org/inputotp/ | |
|
||
| `VFileInput` (rare) | `FileUpload` | https://primevue.org/fileupload/ | `mode='basic'` for inline; default for full uploader |
|
||
| `VColorPicker` (2 uses) | `ColorPicker` | https://primevue.org/colorpicker/ | |
|
||
| `AppDateTimePicker` (Flatpickr) | unchanged | — | Flatpickr is retained per [RFC AD-4](./RFC-WS-FRONTEND-PRIMEVUE.md#ad-4-date-layer--flatpickr-retained-in-fase-1); a thin `DateTimePicker.vue` wrapper composes Flatpickr inside `<FormField>` |
|
||
|
||
**Migration spirit.** Form inputs are the highest-coverage area; nearly
|
||
every input flows through `<FormField>` (§5). Most prop renames are
|
||
trivial (`density` → `size`, `:items` → `:options`). The bigger shift is
|
||
the validation contract: Vuetify's `:rules` array per field is replaced
|
||
by a single Zod schema at the form level, with field-level errors
|
||
surfaced through `<FormField>`. See §5 for the canonical pattern.
|
||
|
||
### 3.2 Layout
|
||
|
||
| Vuetify | Replacement | Notes |
|
||
|---|---|---|
|
||
| `VContainer` | `<div class="max-w-screen-xl mx-auto px-4">` | Tailwind utility composition; no PrimeVue equivalent |
|
||
| `VRow` | `<div class="grid grid-cols-12 gap-4">` | Tailwind grid |
|
||
| `VCol cols=N md=M` | `<div class="col-span-N md:col-span-M">` | Tailwind responsive prefixes |
|
||
| `VCard` | `Card` | https://primevue.org/card/ — slots: `#title`, `#subtitle`, `#content`, `#footer` |
|
||
| `VCardTitle` | Card `#title` slot | Compose inside `Card` |
|
||
| `VCardSubtitle` | Card `#subtitle` slot | |
|
||
| `VCardText` | Card `#content` slot or `<div class="p-6">` | Most uses are pure padding |
|
||
| `VCardActions` | Card `#footer` slot | |
|
||
| `VCardItem` | manual flex layout inside `Card #content` | No direct equivalent |
|
||
| `VDivider` | `Divider` | https://primevue.org/divider/ — `:vertical` → `layout='vertical'` |
|
||
| `VSheet` | `<div>` with Tailwind | No equivalent component |
|
||
| `VSpacer` | `<div class="flex-1">` or surrounding `justify-between` | |
|
||
| `VApp`, `VMain`, `VAppBar`, `VFooter` | layout shell rewrites | See [RFC AD-3](./RFC-WS-FRONTEND-PRIMEVUE.md#ad-3-layout-shell--filename-match-preserved-contents-rewritten); filenames preserved, contents replaced |
|
||
| `VLocaleProvider` | PrimeVue `<ConfigProvider>` (or app-level `app.use(PrimeVue, { locale })`) | See [RFC AD-6](./RFC-WS-FRONTEND-PRIMEVUE.md#ad-6-locale--nl-nl-via-primelocale-vue-i18n-unused) |
|
||
|
||
**Migration spirit.** Most layout in PrimeVue is Tailwind, not components.
|
||
`VRow`/`VCol` translates 1:1 to a 12-column Tailwind grid. `VCard` is the
|
||
biggest semantic preservation — its slot model maps to PrimeVue's `Card`
|
||
slot model with minor renames. Plain `<div>` with Tailwind utilities
|
||
replaces `VSheet`, `VSpacer`, and most uses of `VContainer`.
|
||
|
||
### 3.3 Data display
|
||
|
||
| Vuetify | PrimeVue / Replacement | Reference | Notes |
|
||
|---|---|---|---|
|
||
| `VDataTable` | `DataTable` + `Column` | https://primevue.org/datatable/ | See §6 |
|
||
| `VDataTableServer` | `DataTable` with `:lazy="true"` | https://primevue.org/datatable/#lazy_load | See §6 |
|
||
| `VTable` | `DataTable` (no lazy) | — | For static / pre-loaded tables |
|
||
| `VList` | `Listbox` (selectable) or `DataView` (cards) | https://primevue.org/listbox/ | Context-dependent; selection lists use `Listbox`, card grids use `DataView` |
|
||
| `VListItem` / `VListItemTitle` / `VListItemSubtitle` | `MenuItem` (in menu) or `<li>` (in list) | — | Inside a list, just compose the row markup directly |
|
||
| `VListSubheader` | `<li class="text-sm text-surface-500">` | — | Plain markup |
|
||
| `VChip` | `Tag` (preferred) or `Chip` | https://primevue.org/tag/ | `Tag` for status badges; `Chip` for removable filters |
|
||
| `VAvatar` | `Avatar` | https://primevue.org/avatar/ | `variant='tonal'` → `:style="{ background, color }"` (no built-in tonal) |
|
||
| `VImg` | `<img>` (native) | — | No wrapper needed; use `loading="lazy"` |
|
||
| `VIcon` | `<Icon name="tabler-..." />` | — | **Iconify-Tabler retained** per [RFC AD-5](./RFC-WS-FRONTEND-PRIMEVUE.md#ad-5-icons--iconify-tabler-retained-primeicons-not-installed); thin wrapper at `apps/app/src/components/Icon.vue` over `@iconify/vue`'s `<Icon>`. PrimeIcons is **not installed** |
|
||
| `VLabel` | `<label>` (native) or `<FormField label="...">` | — | |
|
||
| `VTimeline` / `VTimelineItem` | `Timeline` | https://primevue.org/timeline/ | |
|
||
| `VBadge` (1 use) | `Badge` or `OverlayBadge` | https://primevue.org/badge/ | |
|
||
|
||
**Migration spirit.** `VIcon` is the most widespread component (267 uses)
|
||
and it does **not** become a PrimeVue component. The Iconify-Tabler
|
||
naming convention survives intact: call-sites use the existing
|
||
`tabler-*` string identifiers (e.g. `tabler-arrow-right`) and the new
|
||
`apps/app/src/components/Icon.vue` wrapper renders them through
|
||
`@iconify/vue` as real `<svg>` markup. UnoCSS-style `i-tabler-*` utility
|
||
classes were considered but not adopted (UnoCSS is not in the stack);
|
||
the wrapper preserves Crewli continuity. PrimeIcons is not installed;
|
||
do not introduce `pi pi-*` icon classes during F4 — they will not
|
||
render. Lists are the most context-dependent area: a selection list
|
||
uses `Listbox`, a card grid uses `DataView`, a navigation list uses
|
||
`MenuItem`/`PanelMenu`, and a generic display list is just `<ul><li>`.
|
||
|
||
### 3.4 Feedback
|
||
|
||
| Vuetify | PrimeVue | Reference | Notes |
|
||
|---|---|---|---|
|
||
| `VAlert` | `Message` | https://primevue.org/message/ | `type` → `severity`; inline alerts only |
|
||
| `VSnackbar` | `useToast()` + `<Toast>` | https://primevue.org/toast/ | One `<Toast>` mounted in `App.vue`; `useNotificationStore` wraps `useToast()` so call-site API stays stable per [RFC AD-11](./RFC-WS-FRONTEND-PRIMEVUE.md#ad-11-toast-and-confirmdialog-services) |
|
||
| `VProgressLinear` | `ProgressBar` | https://primevue.org/progressbar/ | `:indeterminate` → `mode='indeterminate'` |
|
||
| `VProgressCircular` | `ProgressSpinner` | https://primevue.org/progressspinner/ | |
|
||
| `VSkeletonLoader` | `Skeleton` | https://primevue.org/skeleton/ | `type='card'` has no preset; compose with explicit `width`, `height`, `shape` |
|
||
|
||
**Migration spirit.** `Message` is for inline alerts (rendered in the
|
||
page). Toasts are global, mounted once in `App.vue`, and triggered via
|
||
`useToast()` (or the existing `useNotificationStore` which now wraps it).
|
||
`Skeleton` is more primitive than `VSkeletonLoader` — Vuexy's preset
|
||
shapes (`type='card'`, `type='article'`) need to be reconstructed from
|
||
explicit `Skeleton` elements; do this once as a project-owned
|
||
`<SkeletonCard>` utility if a shape is reused.
|
||
|
||
### 3.5 Navigation
|
||
|
||
| Vuetify | PrimeVue | Reference | Notes |
|
||
|---|---|---|---|
|
||
| `VTabs` / `VTab` / `VWindow` / `VWindowItem` | `Tabs` + `TabList` + `Tab` + `TabPanels` + `TabPanel` | https://primevue.org/tabs/ | Single component family; controlled via `:value` |
|
||
| `VBtnToggle` | `SelectButton` | https://primevue.org/selectbutton/ | |
|
||
| `VPagination` (1 use) | `Paginator` | https://primevue.org/paginator/ | DataTable has built-in paginator; standalone use is rare |
|
||
| `VBreadcrumbs` (none today) | `Breadcrumb` | https://primevue.org/breadcrumb/ | Reserved for future use |
|
||
| `VNavigationDrawer` | layout-shell custom + `PanelMenu` (or `Drawer` for mobile) | https://primevue.org/panelmenu/, https://primevue.org/drawer/ | Sidebar nav is part of the shell rewrite per [RFC AD-3](./RFC-WS-FRONTEND-PRIMEVUE.md#ad-3-layout-shell--filename-match-preserved-contents-rewritten); `PanelMenu` provides accordion behavior equivalent to `VVerticalNavGroup` |
|
||
| Vuexy `AppStepper` | `Stepper` + `StepList` + `Step` + `StepPanels` + `StepPanel` | https://primevue.org/stepper/ | Multi-step wizards (e.g., public registration) |
|
||
| Vuexy `VerticalNavLayout` | layout shell rewrite | — | See [RFC AD-3](./RFC-WS-FRONTEND-PRIMEVUE.md#ad-3-layout-shell--filename-match-preserved-contents-rewritten) |
|
||
|
||
**Migration spirit.** PrimeVue's `Tabs` family expands one Vuetify component
|
||
into five elements (`Tabs` / `TabList` / `Tab` / `TabPanels` / `TabPanel`).
|
||
This is more verbose at call sites but more flexible (e.g., tab list can
|
||
live separately from panels). The sidebar / nav shell is a rewrite, not
|
||
a component swap — Vuexy's `@layouts/` system is replaced wholesale per
|
||
RFC AD-3, but route-level nav semantics (Pinia stores, route meta) stay
|
||
the same.
|
||
|
||
### 3.6 Overlays
|
||
|
||
| Vuetify | PrimeVue | Reference | Notes |
|
||
|---|---|---|---|
|
||
| `VDialog` | `Dialog` | https://primevue.org/dialog/ | `:max-width='500'` → `:style="{ width: '500px' }"`; `:fullscreen` → `:maximizable` |
|
||
| `VMenu` | `Menu` (action lists) or `Popover` (rich content) | https://primevue.org/menu/, https://primevue.org/popover/ | Choose `Menu` for `MenuItem` lists, `Popover` for arbitrary slot content |
|
||
| `VTooltip` | `v-tooltip` directive | https://primevue.org/tooltip/ | Directive form (`v-tooltip="'Hint'"`) is preferred over a wrapping component |
|
||
| `VOverlay` | `Dialog` with `modal` prop, or custom `<div>` | — | Most overlay uses are modal dialogs |
|
||
| `VBottomSheet` (none today) | `Drawer` with `position='bottom'` | https://primevue.org/drawer/ | Reserved; mobile bottom-sheets if needed |
|
||
| `VExpansionPanel(s)` | `Accordion` + `AccordionPanel` + `AccordionHeader` + `AccordionContent` | https://primevue.org/accordion/ | Renamed in v4 from `AccordionTab` |
|
||
| `VExpandTransition` / `VScaleTransition` | Vue `<Transition>` (native) | — | Native transitions; Vuetify-specific transitions removed |
|
||
| Native `confirm()` / Vuexy `ConfirmDialog` | `useConfirm()` + `<ConfirmDialog>` | https://primevue.org/confirmdialog/ | Mounted once in `App.vue` per [RFC AD-11](./RFC-WS-FRONTEND-PRIMEVUE.md#ad-11-toast-and-confirmdialog-services) |
|
||
|
||
**Migration spirit.** Overlays are services in PrimeVue: `useToast()`,
|
||
`useConfirm()`, `useDialog()`. The pattern is one mount in `App.vue`,
|
||
many calls from anywhere. `Dialog` itself is still a component (not a
|
||
service) for non-confirm modals — keep `<Dialog v-model:visible="...">`
|
||
for create/edit forms.
|
||
|
||
---
|
||
|
||
## 4. Aura theme and Crewli tokens
|
||
|
||
PrimeVue 4 uses CSS custom properties under the `--p-*` prefix, generated
|
||
from a JS preset. Crewli's preset extends Aura and overrides only the
|
||
primary color (teal) and dark-mode contrast colors. Full token plan is in
|
||
[RFC Appendix B](./RFC-WS-FRONTEND-PRIMEVUE.md#appendix-b--aura-theme-token-plan); F3 implements
|
||
`apps/app/src/plugins/primevue/theme.ts`.
|
||
|
||
Key conventions:
|
||
|
||
- **Primary color** is Crewli teal (`#0D9394`), defined as `primary.500` in the preset
|
||
- **Dark mode** uses `darkModeSelector: '.dark'` (matches Vuexy's existing dark-class strategy on `<html>`); the same toggle that flips Vuetify's dark theme today flips PrimeVue's, so no UI change for users
|
||
- **Surface, formField, list, navigation, overlay, content** tokens use Aura defaults (no override needed)
|
||
- **Brand-color customization** at the component level happens via the `pt` API (§7), not via CSS overrides
|
||
|
||
Do **not** introduce `--p-*` overrides directly in SCSS during F4. If a
|
||
brand adjustment is needed, extend the preset in `theme.ts` so the
|
||
override is centralized and dark-mode-aware.
|
||
|
||
---
|
||
|
||
## 5. Forms pattern (canonical)
|
||
|
||
The form layer is `@primevue/forms` plus a Zod resolver, called through a
|
||
Crewli-owned `<FormField>` wrapper at
|
||
`apps/app/src/components/forms/FormField.vue`.
|
||
|
||
**The full API specification — usage example, prop contract, slot
|
||
contract, `useFormError` 422 integration, error precedence — lives in
|
||
[RFC-WS-FRONTEND-PRIMEVUE Appendix A](./RFC-WS-FRONTEND-PRIMEVUE.md#appendix-a--formfield-api-specification).**
|
||
That is the single source of truth; it is intentionally not duplicated
|
||
here so that revisions to the API only need to land in one place.
|
||
|
||
What this document encodes (Crewli conventions on top of the API):
|
||
|
||
1. **One Zod schema per form**, defined at the top of the component or hoisted to `apps/app/src/schemas/[module].ts` if reused. Schema field names mirror the backend Form Request field names exactly (snake_case) so 422 errors map back without translation.
|
||
2. **`<FormField name="..." label="..." required>`** wraps every PrimeVue input. The wrapper's `name` prop matches the Zod schema key and the input's `:name` prop. Labels are Dutch by Crewli convention.
|
||
3. **Server validation (422)**: catch in the `onSubmit` handler, call `applyApiErrors(e.response.data.errors)` from `useFormError(formRef)`. Field-level messages are injected via `<FormField :apiError="...">`; manual `errors` refs are not used.
|
||
4. **No mixed validation**. A single form is either fully Zod-validated (target) or fully `:rules`-validated (legacy Vuetify). Do not introduce a hybrid.
|
||
5. **Migration phase**: existing forms keep their current `ref({}) + VForm + :rules + errors` pattern until the surrounding surface is migrated. Per F4 sub-package, all forms on that surface flip to PrimeVue + `<FormField>` + Zod resolver in one commit. See §9.
|
||
|
||
VeeValidate is **not** the form library. It was previously listed in
|
||
`CLAUDE.md` but never adopted; the form layer is `@primevue/forms` + Zod
|
||
resolver per [RFC AD-1](./RFC-WS-FRONTEND-PRIMEVUE.md#ad-1-form-layer--primevueforms--zod-resolver).
|
||
|
||
---
|
||
|
||
## 6. DataTable pattern
|
||
|
||
`<DataTable>` replaces both `VDataTable` (client-side) and
|
||
`VDataTableServer` (server-side). The full DataTable inventory is in
|
||
[MIGRATION-AUDIT-PRIMEVUE §6](./MIGRATION-AUDIT-PRIMEVUE.md#6-datatable-inventory-critical-for-migration);
|
||
the strategy is in [RFC AD-7](./RFC-WS-FRONTEND-PRIMEVUE.md#ad-7-datatable-strategy).
|
||
|
||
Conventions:
|
||
|
||
| Need | PrimeVue API | Notes |
|
||
|---|---|---|
|
||
| Server-side pagination/sort/filter | `:lazy="true"`, `:totalRecords`, `@page` / `@sort` / `@filter` event handlers | Replaces `VDataTableServer`'s `:items-length` + `@update:options` |
|
||
| Custom cell rendering | `<Column field="..."><template #body="slotProps">...</template></Column>` | Replaces Vuetify's `#item.fieldName` slot |
|
||
| Custom header rendering | `<Column><template #header>...</template></Column>` | Replaces `#header.fieldName` |
|
||
| Expandable rows | `expandedRows` v-model + `<template #expansion>` | https://primevue.org/datatable/#row_expand |
|
||
| Row selection | `selection` v-model + `selectionMode="single"` / `"multiple"` | |
|
||
| Virtual scrolling (>1000 rows) | `:virtualScrollerOptions="{ itemSize: 50 }"` | Replaces `v-data-table-virtual` |
|
||
| Empty / loading / error states | `<template #empty>`, `:loading`, surrounding error wrapper | The three-state pattern (loading / error / empty) per [`CLAUDE.md` UI rules](../CLAUDE.md) is preserved |
|
||
|
||
**Slot translation cheat sheet:**
|
||
|
||
| `VDataTable` slot | `DataTable` equivalent |
|
||
|---|---|
|
||
| `#item.email` | `<Column field="email"><template #body="{ data }">{{ data.email }}</template></Column>` |
|
||
| `#header.actions` | `<Column><template #header>Acties</template></Column>` |
|
||
| `#expanded-row` | `<template #expansion="{ data }">...</template>` |
|
||
| `#no-data` | `<template #empty>...</template>` |
|
||
| `#loading` | `:loading` prop + `loadingIcon` |
|
||
| `#top` | sibling `<div>` above `<DataTable>`; not a built-in slot |
|
||
|
||
**Per-page conventions:**
|
||
|
||
- One `<DataTable>` instance per page; no nested data tables
|
||
- Server-side tables wire to a TanStack Query composable that returns `{ data, isLoading, isError, refetch }`; the composable's params (`page`, `perPage`, `sortBy`, `filters`) are driven by reactive refs that PrimeVue's `@page` / `@sort` / `@filter` handlers update
|
||
- Column widths are **not** set in props for the common case; use `pt` on `<Column>` only when a fixed width is structurally needed
|
||
- Selection state is a Pinia store **only** if it crosses components (e.g., bulk-action toolbar); inline selection stays local
|
||
|
||
---
|
||
|
||
## 7. Pass-through (`pt`) API and slot conventions
|
||
|
||
PrimeVue's primary customization mechanism is `pt` (pass-through), which
|
||
targets named DOM nodes inside a component and applies attributes/classes
|
||
to them.
|
||
|
||
```vue
|
||
<Button label="Opslaan" :pt="{
|
||
root: { class: 'shadow-md' },
|
||
label: { class: 'font-semibold' }
|
||
}" />
|
||
```
|
||
|
||
Decision matrix — when to reach for which tool:
|
||
|
||
| Need | Use |
|
||
|---|---|
|
||
| Layout, spacing, typography around a component | Tailwind utility classes on the wrapping `<div>` |
|
||
| Internal styling of a component's DOM (e.g., the `Button`'s root `<button>`) | `pt` |
|
||
| Brand color, dark-mode-aware token | Aura preset extension in `theme.ts` (§4) |
|
||
| One-off override that doesn't fit the above | `<style scoped>` as last resort, with a comment explaining why |
|
||
|
||
Slot conventions vs. Vuetify:
|
||
|
||
- Most named slots map 1:1 in name (`#default`, `#footer`), but slot **prop shapes** differ — always check the PrimeVue docs page for the exact slot signature
|
||
- PrimeVue's slot system is generally narrower than Vuetify's; deep customization that Vuetify did via slots is done in PrimeVue via `pt`
|
||
- Default slots receive no implicit data in most components — read the doc page if a slot returns nothing useful
|
||
|
||
---
|
||
|
||
## 8. Tailwind v4 + PrimeVue integration
|
||
|
||
Tailwind v4 with the `tailwindcss-primeui` plugin is installed alongside
|
||
PrimeVue per [RFC AD-12](./RFC-WS-FRONTEND-PRIMEVUE.md#ad-12-tailwind-css-v4-added-for-layout-utilities).
|
||
Configuration lives in F3.
|
||
|
||
Source-order conventions (CSS cascade matters):
|
||
|
||
1. PrimeVue base styles (auto-injected by the plugin)
|
||
2. Aura theme tokens (from `theme.ts`)
|
||
3. `tailwindcss-primeui` exposes Aura tokens as Tailwind utilities (`bg-primary`, `text-surface-500`, etc.)
|
||
4. Tailwind utility classes in templates
|
||
5. Project-scoped CSS (last resort)
|
||
|
||
When to reach for what:
|
||
|
||
| Need | Tool |
|
||
|---|---|
|
||
| Margin, padding, flex, grid, gap | Tailwind utility class |
|
||
| Text color, background, border using brand tokens | Tailwind utility from `tailwindcss-primeui` (`bg-primary-500`, `text-surface-700`) |
|
||
| Component-internal restyle | `pt` on the component |
|
||
| Brand-wide override | Aura preset extension |
|
||
|
||
Avoid mixing arbitrary HEX values into templates. If a color is reused
|
||
twice, it belongs in the Aura preset; reference it via the surface /
|
||
primary scale token instead of literal HEX.
|
||
|
||
---
|
||
|
||
## 9. Migration phase guidance
|
||
|
||
During F4 (sub-packages F4a–F4d), the SPA contains both Vuetify and
|
||
PrimeVue components in the same build. The discipline that keeps this
|
||
sane:
|
||
|
||
1. **Surface-level consistency.** Within one route or component tree, use one framework. Never mix `<VTextField>` and `<InputText>` in the same form, never mix `<VBtn>` and `<Button>` in the same toolbar. The unit of migration is a surface (a route or a feature folder), committed as a sub-package.
|
||
2. **No back-porting.** Do not migrate "just one component" inside a not-yet-migrated surface. The sub-package is the smallest migration unit.
|
||
3. **Pre-migration: follow Vuetify.** If you are extending an un-migrated surface during F4 (e.g., bug fix on `/portal/*` while F4b is in progress on the organizer root), follow the conventions visible in the surrounding code. The pre-F2 `VUEXY_COMPONENTS.md` content is reachable at git commit `1c449ff6204cae6371da08c34ea8934d6b2ffcb8` for reference.
|
||
4. **Post-migration: follow PrimeVue.** Any new code on a migrated surface follows this document. New surfaces (created during or after F4) start in PrimeVue.
|
||
5. **Tests during migration.** Vuetify stays in test infrastructure for surfaces that haven't migrated yet. See [ARCH-TESTING.md §7](./ARCH-TESTING.md) for the test-runtime story; `setupFile` flips per [RFC AD-10](./RFC-WS-FRONTEND-PRIMEVUE.md#ad-10-test-runtime--single-setupfile-flip).
|
||
6. **When in doubt, prefer PrimeVue conventions.** If a form on a migrated surface needs a component this document doesn't yet cover, find the closest PrimeVue equivalent on https://primevue.org/, implement it, and append a note in §3 (or open a tightly scoped PR to update this document). F4 sub-packages are expected to refine §3 — that is the explicit growth path.
|
||
7. **F6 is the cliff.** After F6 (cleanup, ~0.5 day), Vuetify is removed from `package.json` and `<V*>` tags fail to compile. No more parallel mode. Per [RFC §9](./RFC-WS-FRONTEND-PRIMEVUE.md#9-rollback-plan), F6 is the point of no return for rollback.
|
||
|
||
---
|
||
|
||
## 10. Cross-references and resources
|
||
|
||
External (PrimeVue ecosystem):
|
||
|
||
- PrimeVue components: https://primevue.org/
|
||
- `@primevue/forms` (form layer): https://primevue.org/forms/
|
||
- `@primeuix/themes` (Aura preset, the maintained successor to `@primevue/themes`): https://primevue.org/theming/styled/
|
||
- `tailwindcss-primeui` (token bridge): https://github.com/primefaces/tailwindcss-primeui
|
||
|
||
Internal (Crewli docs):
|
||
|
||
- [RFC-WS-FRONTEND-PRIMEVUE.md](./RFC-WS-FRONTEND-PRIMEVUE.md) — full migration plan, AD-1 to AD-12, sprint breakdown, risk register, FormField API (Appendix A), Aura theme tokens (Appendix B), version pinning policy (Appendix C)
|
||
- [MIGRATION-AUDIT-PRIMEVUE.md](./MIGRATION-AUDIT-PRIMEVUE.md) — F1 audit: Vuetify usage inventory (§1), `@core` / `@layouts` surface area (§2), Vuetify config and theme (§3), form-layer inventory (§4), page and route inventory (§5), DataTable inventory (§6)
|
||
- [ARCH-TESTING.md](./ARCH-TESTING.md) — test-tier decision tree; §7 covers the Vuetify-in-test-infra temporary state during the migration
|
||
- [VUEXY_COMPONENTS.md](./VUEXY_COMPONENTS.md) — deprecation stub; pre-F2 content recoverable via git commit `1c449ff6204cae6371da08c34ea8934d6b2ffcb8`
|
||
- [CLAUDE.md](../CLAUDE.md) — project-wide conventions; UI section now points here
|
||
|
||
This document is a foundation. F4 sub-packages will extend §3 with
|
||
component-level migration notes as real surfaces are converted; expect
|
||
diff PRs against this file alongside each sub-package commit.
|