397 lines
26 KiB
Markdown
397 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.
|
||
|
||
> **GUI redesign:** the page-migration phase is governed by
|
||
> `dev-docs/RFC-WS-GUI-REDESIGN-CREWLI-STARTER.md` (supersedes F4a–F4d).
|
||
> This component reference still applies to all v2 work.
|
||
|
||
**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.
|
||
|
||
Anti-pattern: directly applying responsive visibility utility classes (md:hidden, lg:flex, etc.) to PrimeVue components. PrimeVue's component CSS may override due to cascade order. Always wrap in a plain element that owns the visibility class.
|
||
|
||
---
|
||
|
||
## 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.
|