@elyracode/stack-primevue 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/extensions/index.ts +13 -0
- package/package.json +27 -0
- package/skills/primevue/SKILL.md +1199 -0
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# @elyracode/stack-primevue
|
|
2
|
+
|
|
3
|
+
Elyra extension for **PrimeVue 4** -- 80+ UI components for Vue 3 with Laravel/Inertia.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
elyra install npm:@elyracode/stack-primevue
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## What's included
|
|
12
|
+
|
|
13
|
+
- **Skills**: Comprehensive PrimeVue 4 component reference (DataTable, Dialog, Forms, Navigation, Toast, ConfirmDialog, theming, Laravel Inertia patterns, gotchas)
|
|
14
|
+
- **Commands**: `/primevue:info` -- show profile status
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
Just ask Elyra about PrimeVue components:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
> Build a products CRUD page with DataTable and Dialog
|
|
22
|
+
> Add a filterable sortable table with pagination
|
|
23
|
+
> Create a form with validation using PrimeVue inputs
|
|
24
|
+
> Set up PrimeVue with the Aura theme in my Laravel app
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The agent knows PrimeVue's component API, import patterns, theming system, and how to integrate with Laravel Inertia.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@elyracode/coding-agent";
|
|
2
|
+
|
|
3
|
+
export default function (elyra: ExtensionAPI) {
|
|
4
|
+
// The primevue skill is auto-loaded from the skills/ directory.
|
|
5
|
+
// This extension adds stack-aware commands.
|
|
6
|
+
|
|
7
|
+
elyra.registerCommand("primevue:info", {
|
|
8
|
+
description: "Show PrimeVue stack profile information",
|
|
9
|
+
handler: async (_args, ctx) => {
|
|
10
|
+
ctx.ui.notify("PrimeVue 4 profile loaded. Skills: primevue. Use /skill:primevue for full component reference.");
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@elyracode/stack-primevue",
|
|
3
|
+
"version": "0.3.5",
|
|
4
|
+
"description": "Elyra extension for PrimeVue 4 -- 80+ UI components for Vue 3 with Laravel/Inertia",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"keywords": ["elyra-package", "primevue", "vue", "laravel", "inertia", "ui-components"],
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"author": "Knut W. Horne",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/kwhorne/elyra.git",
|
|
12
|
+
"directory": "packages/stack-primevue"
|
|
13
|
+
},
|
|
14
|
+
"elyra": {
|
|
15
|
+
"skills": ["./skills"],
|
|
16
|
+
"extensions": ["./extensions/index.ts"]
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"@elyracode/coding-agent": "*",
|
|
20
|
+
"typebox": "*"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"clean": "echo 'nothing to clean'",
|
|
24
|
+
"build": "echo 'nothing to build'",
|
|
25
|
+
"check": "echo 'nothing to check'"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,1199 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: primevue
|
|
3
|
+
description: Deep knowledge about PrimeVue 4 UI component library for Vue 3. Use when working with PrimeVue components, theming, forms, data tables, dialogs, or any PrimeVue UI patterns in Vue/Laravel/Inertia projects.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# PrimeVue 4 Reference
|
|
7
|
+
|
|
8
|
+
## Setup
|
|
9
|
+
|
|
10
|
+
### Installation
|
|
11
|
+
```bash
|
|
12
|
+
npm install primevue @primeuix/themes primeicons
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Plugin Configuration
|
|
16
|
+
```typescript
|
|
17
|
+
// main.ts or app.ts
|
|
18
|
+
import { createApp } from 'vue'
|
|
19
|
+
import PrimeVue from 'primevue/config'
|
|
20
|
+
import Aura from '@primeuix/themes/aura'
|
|
21
|
+
import 'primeicons/primeicons.css'
|
|
22
|
+
|
|
23
|
+
const app = createApp(App)
|
|
24
|
+
app.use(PrimeVue, {
|
|
25
|
+
theme: {
|
|
26
|
+
preset: Aura
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### With Laravel Inertia
|
|
32
|
+
```typescript
|
|
33
|
+
// resources/js/app.ts
|
|
34
|
+
import { createApp, h } from 'vue'
|
|
35
|
+
import { createInertiaApp } from '@inertiajs/vue3'
|
|
36
|
+
import PrimeVue from 'primevue/config'
|
|
37
|
+
import Aura from '@primeuix/themes/aura'
|
|
38
|
+
import ToastService from 'primevue/toastservice'
|
|
39
|
+
import ConfirmationService from 'primevue/confirmationservice'
|
|
40
|
+
import 'primeicons/primeicons.css'
|
|
41
|
+
|
|
42
|
+
createInertiaApp({
|
|
43
|
+
resolve: name => {
|
|
44
|
+
const pages = import.meta.glob('./Pages/**/*.vue', { eager: true })
|
|
45
|
+
return pages[`./Pages/${name}.vue`]
|
|
46
|
+
},
|
|
47
|
+
setup({ el, App, props, plugin }) {
|
|
48
|
+
const app = createApp({ render: () => h(App, props) })
|
|
49
|
+
app.use(plugin)
|
|
50
|
+
app.use(PrimeVue, { theme: { preset: Aura } })
|
|
51
|
+
app.use(ToastService)
|
|
52
|
+
app.use(ConfirmationService)
|
|
53
|
+
app.mount(el)
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Available Theme Presets
|
|
59
|
+
- `Aura` -- Modern, clean default theme
|
|
60
|
+
- `Material` -- Material Design inspired
|
|
61
|
+
- `Lara` -- Bootstrap-like familiar style
|
|
62
|
+
- `Nora` -- Minimal, subtle design
|
|
63
|
+
|
|
64
|
+
Import any preset from `@primeuix/themes/<preset-name-lowercase>`:
|
|
65
|
+
```typescript
|
|
66
|
+
import Aura from '@primeuix/themes/aura'
|
|
67
|
+
import Material from '@primeuix/themes/material'
|
|
68
|
+
import Lara from '@primeuix/themes/lara'
|
|
69
|
+
import Nora from '@primeuix/themes/nora'
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Component Import Pattern
|
|
73
|
+
|
|
74
|
+
PrimeVue uses individual component imports for tree-shaking. There is no barrel export -- import each component from its own path:
|
|
75
|
+
|
|
76
|
+
```vue
|
|
77
|
+
<script setup lang="ts">
|
|
78
|
+
import Button from 'primevue/button'
|
|
79
|
+
import DataTable from 'primevue/datatable'
|
|
80
|
+
import Column from 'primevue/column'
|
|
81
|
+
import InputText from 'primevue/inputtext'
|
|
82
|
+
import Dialog from 'primevue/dialog'
|
|
83
|
+
</script>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Core Components
|
|
87
|
+
|
|
88
|
+
### Form Components
|
|
89
|
+
|
|
90
|
+
#### InputText
|
|
91
|
+
```vue
|
|
92
|
+
<InputText v-model="name" placeholder="Enter name" />
|
|
93
|
+
<InputText v-model="email" type="email" :invalid="!!errors.email" />
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
#### Password
|
|
97
|
+
```vue
|
|
98
|
+
<Password v-model="password" toggleMask :feedback="true" />
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
#### Textarea
|
|
102
|
+
```vue
|
|
103
|
+
<Textarea v-model="description" rows="5" cols="30" autoResize />
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
#### Select (Dropdown)
|
|
107
|
+
```vue
|
|
108
|
+
<Select v-model="selectedCity" :options="cities" optionLabel="name"
|
|
109
|
+
optionValue="code" placeholder="Select a city" />
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
With grouped options:
|
|
113
|
+
```vue
|
|
114
|
+
<Select v-model="selectedCity" :options="groupedCities" optionLabel="label"
|
|
115
|
+
optionValue="value" optionGroupLabel="label" optionGroupChildren="items"
|
|
116
|
+
placeholder="Select a city" />
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
#### AutoComplete
|
|
120
|
+
```vue
|
|
121
|
+
<AutoComplete v-model="value" :suggestions="filteredItems"
|
|
122
|
+
@complete="search($event)" field="name" placeholder="Search..." />
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
#### MultiSelect
|
|
126
|
+
```vue
|
|
127
|
+
<MultiSelect v-model="selectedCities" :options="cities" optionLabel="name"
|
|
128
|
+
placeholder="Select Cities" :maxSelectedLabels="3" display="chip" />
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
#### DatePicker (Calendar)
|
|
132
|
+
```vue
|
|
133
|
+
<DatePicker v-model="date" dateFormat="yy-mm-dd" showIcon />
|
|
134
|
+
<DatePicker v-model="dateRange" selectionMode="range" :manualInput="false" />
|
|
135
|
+
<DatePicker v-model="dateTime" showTime hourFormat="24" />
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
#### Checkbox
|
|
139
|
+
```vue
|
|
140
|
+
<!-- Binary -->
|
|
141
|
+
<Checkbox v-model="accept" :binary="true" inputId="accept" />
|
|
142
|
+
<label for="accept">I accept</label>
|
|
143
|
+
|
|
144
|
+
<!-- Multiple values -->
|
|
145
|
+
<Checkbox v-model="selectedCategories" :value="category" inputId="cat1" />
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
#### RadioButton
|
|
149
|
+
```vue
|
|
150
|
+
<div v-for="option in options" :key="option.value">
|
|
151
|
+
<RadioButton v-model="selected" :value="option.value" :inputId="option.value" />
|
|
152
|
+
<label :for="option.value">{{ option.label }}</label>
|
|
153
|
+
</div>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
#### InputNumber
|
|
157
|
+
```vue
|
|
158
|
+
<InputNumber v-model="price" mode="currency" currency="USD" locale="en-US" />
|
|
159
|
+
<InputNumber v-model="quantity" :min="0" :max="100" showButtons />
|
|
160
|
+
<InputNumber v-model="percent" prefix="%" :minFractionDigits="2" />
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
#### ToggleSwitch
|
|
164
|
+
```vue
|
|
165
|
+
<ToggleSwitch v-model="checked" />
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
#### Slider
|
|
169
|
+
```vue
|
|
170
|
+
<Slider v-model="value" :min="0" :max="100" />
|
|
171
|
+
<Slider v-model="rangeValues" range :min="0" :max="100" />
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
#### ColorPicker
|
|
175
|
+
```vue
|
|
176
|
+
<ColorPicker v-model="color" />
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
#### InputMask
|
|
180
|
+
```vue
|
|
181
|
+
<InputMask v-model="phone" mask="(999) 999-9999" placeholder="(999) 999-9999" />
|
|
182
|
+
<InputMask v-model="ssn" mask="999-99-9999" />
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
#### Chips
|
|
186
|
+
```vue
|
|
187
|
+
<Chips v-model="tags" separator="," placeholder="Add tags" />
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
#### Editor (rich text)
|
|
191
|
+
```vue
|
|
192
|
+
<Editor v-model="content" editorStyle="height: 320px" />
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
#### FileUpload
|
|
196
|
+
```vue
|
|
197
|
+
<!-- Basic -->
|
|
198
|
+
<FileUpload mode="basic" accept="image/*" :maxFileSize="1000000"
|
|
199
|
+
@upload="onUpload" :auto="true" chooseLabel="Upload" />
|
|
200
|
+
|
|
201
|
+
<!-- Advanced with drag-and-drop -->
|
|
202
|
+
<FileUpload name="files[]" url="/api/upload" :multiple="true"
|
|
203
|
+
accept="image/*" :maxFileSize="5000000">
|
|
204
|
+
<template #empty>
|
|
205
|
+
<p>Drag and drop files here to upload.</p>
|
|
206
|
+
</template>
|
|
207
|
+
</FileUpload>
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Data Display
|
|
211
|
+
|
|
212
|
+
#### DataTable
|
|
213
|
+
```vue
|
|
214
|
+
<script setup lang="ts">
|
|
215
|
+
import DataTable from 'primevue/datatable'
|
|
216
|
+
import Column from 'primevue/column'
|
|
217
|
+
|
|
218
|
+
interface Product {
|
|
219
|
+
id: number
|
|
220
|
+
name: string
|
|
221
|
+
price: number
|
|
222
|
+
category: string
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const props = defineProps<{ products: Product[] }>()
|
|
226
|
+
</script>
|
|
227
|
+
|
|
228
|
+
<template>
|
|
229
|
+
<DataTable :value="products" stripedRows paginator :rows="10"
|
|
230
|
+
:rowsPerPageOptions="[5, 10, 20, 50]"
|
|
231
|
+
sortMode="multiple" removableSort
|
|
232
|
+
v-model:filters="filters" filterDisplay="row">
|
|
233
|
+
<Column field="name" header="Name" sortable>
|
|
234
|
+
<template #filter="{ filterModel, filterCallback }">
|
|
235
|
+
<InputText v-model="filterModel.value" @input="filterCallback()" placeholder="Search" />
|
|
236
|
+
</template>
|
|
237
|
+
</Column>
|
|
238
|
+
<Column field="price" header="Price" sortable>
|
|
239
|
+
<template #body="{ data }">
|
|
240
|
+
{{ formatCurrency(data.price) }}
|
|
241
|
+
</template>
|
|
242
|
+
</Column>
|
|
243
|
+
<Column field="category" header="Category" sortable />
|
|
244
|
+
<Column header="Actions">
|
|
245
|
+
<template #body="{ data }">
|
|
246
|
+
<Button icon="pi pi-pencil" severity="info" text rounded @click="edit(data)" />
|
|
247
|
+
<Button icon="pi pi-trash" severity="danger" text rounded @click="remove(data)" />
|
|
248
|
+
</template>
|
|
249
|
+
</Column>
|
|
250
|
+
</DataTable>
|
|
251
|
+
</template>
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
#### DataTable Filter Initialization
|
|
255
|
+
```typescript
|
|
256
|
+
import { FilterMatchMode } from '@primevue/core/api'
|
|
257
|
+
|
|
258
|
+
const filters = ref({
|
|
259
|
+
global: { value: null, matchMode: FilterMatchMode.CONTAINS },
|
|
260
|
+
name: { value: null, matchMode: FilterMatchMode.STARTS_WITH },
|
|
261
|
+
category: { value: null, matchMode: FilterMatchMode.EQUALS },
|
|
262
|
+
})
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
#### DataTable with Selection
|
|
266
|
+
```vue
|
|
267
|
+
<DataTable v-model:selection="selectedProducts" :value="products"
|
|
268
|
+
selectionMode="multiple" dataKey="id">
|
|
269
|
+
<Column selectionMode="multiple" headerStyle="width: 3rem" />
|
|
270
|
+
<Column field="name" header="Name" />
|
|
271
|
+
</DataTable>
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
#### DataTable with Expandable Rows
|
|
275
|
+
```vue
|
|
276
|
+
<DataTable :value="products" v-model:expandedRows="expandedRows" dataKey="id">
|
|
277
|
+
<Column expander style="width: 5rem" />
|
|
278
|
+
<Column field="name" header="Name" />
|
|
279
|
+
<template #expansion="{ data }">
|
|
280
|
+
<div class="p-4">
|
|
281
|
+
<h5>Orders for {{ data.name }}</h5>
|
|
282
|
+
<DataTable :value="data.orders">
|
|
283
|
+
<Column field="id" header="Id" />
|
|
284
|
+
<Column field="amount" header="Amount" />
|
|
285
|
+
</DataTable>
|
|
286
|
+
</div>
|
|
287
|
+
</template>
|
|
288
|
+
</DataTable>
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
#### DataTable with Lazy Loading (server-side)
|
|
292
|
+
```vue
|
|
293
|
+
<DataTable :value="products" :lazy="true" :totalRecords="totalRecords"
|
|
294
|
+
:loading="loading" @page="onPage" @sort="onSort" @filter="onFilter"
|
|
295
|
+
paginator :rows="10" dataKey="id"
|
|
296
|
+
v-model:filters="filters" filterDisplay="row">
|
|
297
|
+
<Column field="name" header="Name" sortable />
|
|
298
|
+
</DataTable>
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
function onPage(event: DataTablePageEvent) {
|
|
303
|
+
router.get(route('products.index'), {
|
|
304
|
+
page: event.page + 1,
|
|
305
|
+
per_page: event.rows,
|
|
306
|
+
}, { preserveState: true })
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
#### DataTable with Inline Editing
|
|
311
|
+
```vue
|
|
312
|
+
<DataTable :value="products" editMode="cell" @cell-edit-complete="onCellEditComplete">
|
|
313
|
+
<Column field="name" header="Name">
|
|
314
|
+
<template #editor="{ data, field }">
|
|
315
|
+
<InputText v-model="data[field]" autofocus />
|
|
316
|
+
</template>
|
|
317
|
+
</Column>
|
|
318
|
+
<Column field="price" header="Price">
|
|
319
|
+
<template #editor="{ data, field }">
|
|
320
|
+
<InputNumber v-model="data[field]" mode="currency" currency="USD" />
|
|
321
|
+
</template>
|
|
322
|
+
</Column>
|
|
323
|
+
</DataTable>
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
#### Tree
|
|
327
|
+
```vue
|
|
328
|
+
<Tree :value="nodes" selectionMode="checkbox" v-model:selectionKeys="selectedKeys" />
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
#### TreeTable
|
|
332
|
+
```vue
|
|
333
|
+
<TreeTable :value="nodes">
|
|
334
|
+
<Column field="name" header="Name" expander />
|
|
335
|
+
<Column field="size" header="Size" />
|
|
336
|
+
<Column field="type" header="Type" />
|
|
337
|
+
</TreeTable>
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
#### Timeline
|
|
341
|
+
```vue
|
|
342
|
+
<Timeline :value="events">
|
|
343
|
+
<template #content="{ item }">
|
|
344
|
+
<Card>
|
|
345
|
+
<template #title>{{ item.status }}</template>
|
|
346
|
+
<template #content>{{ item.date }}</template>
|
|
347
|
+
</Card>
|
|
348
|
+
</template>
|
|
349
|
+
</Timeline>
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
#### OrderList
|
|
353
|
+
```vue
|
|
354
|
+
<OrderList v-model="products" dataKey="id">
|
|
355
|
+
<template #option="{ option }">
|
|
356
|
+
<div class="flex items-center gap-2">
|
|
357
|
+
<span>{{ option.name }}</span>
|
|
358
|
+
</div>
|
|
359
|
+
</template>
|
|
360
|
+
</OrderList>
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Overlay Components
|
|
364
|
+
|
|
365
|
+
#### Dialog
|
|
366
|
+
```vue
|
|
367
|
+
<Dialog v-model:visible="visible" header="Edit Product" :style="{ width: '450px' }" modal>
|
|
368
|
+
<div class="flex flex-col gap-4">
|
|
369
|
+
<div>
|
|
370
|
+
<label class="font-semibold">Name</label>
|
|
371
|
+
<InputText v-model="product.name" class="w-full" />
|
|
372
|
+
</div>
|
|
373
|
+
<div>
|
|
374
|
+
<label class="font-semibold">Price</label>
|
|
375
|
+
<InputNumber v-model="product.price" mode="currency" currency="USD" class="w-full" />
|
|
376
|
+
</div>
|
|
377
|
+
</div>
|
|
378
|
+
<template #footer>
|
|
379
|
+
<Button label="Cancel" severity="secondary" @click="visible = false" />
|
|
380
|
+
<Button label="Save" @click="save" />
|
|
381
|
+
</template>
|
|
382
|
+
</Dialog>
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
#### ConfirmDialog
|
|
386
|
+
```vue
|
|
387
|
+
<script setup>
|
|
388
|
+
import { useConfirm } from 'primevue/useconfirm'
|
|
389
|
+
const confirm = useConfirm()
|
|
390
|
+
|
|
391
|
+
function confirmDelete() {
|
|
392
|
+
confirm.require({
|
|
393
|
+
message: 'Are you sure you want to delete this?',
|
|
394
|
+
header: 'Confirm',
|
|
395
|
+
icon: 'pi pi-exclamation-triangle',
|
|
396
|
+
rejectProps: { label: 'Cancel', severity: 'secondary' },
|
|
397
|
+
acceptProps: { label: 'Delete', severity: 'danger' },
|
|
398
|
+
accept: () => { /* delete logic */ },
|
|
399
|
+
})
|
|
400
|
+
}
|
|
401
|
+
</script>
|
|
402
|
+
<template>
|
|
403
|
+
<ConfirmDialog />
|
|
404
|
+
<Button label="Delete" severity="danger" @click="confirmDelete" />
|
|
405
|
+
</template>
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
#### ConfirmPopup
|
|
409
|
+
```vue
|
|
410
|
+
<script setup>
|
|
411
|
+
import { useConfirm } from 'primevue/useconfirm'
|
|
412
|
+
const confirm = useConfirm()
|
|
413
|
+
|
|
414
|
+
function confirmAction(event: Event) {
|
|
415
|
+
confirm.require({
|
|
416
|
+
target: event.currentTarget as HTMLElement,
|
|
417
|
+
message: 'Proceed?',
|
|
418
|
+
accept: () => { /* proceed */ },
|
|
419
|
+
})
|
|
420
|
+
}
|
|
421
|
+
</script>
|
|
422
|
+
<template>
|
|
423
|
+
<ConfirmPopup />
|
|
424
|
+
<Button label="Confirm" @click="confirmAction($event)" />
|
|
425
|
+
</template>
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
#### Toast
|
|
429
|
+
```vue
|
|
430
|
+
<script setup>
|
|
431
|
+
import { useToast } from 'primevue/usetoast'
|
|
432
|
+
const toast = useToast()
|
|
433
|
+
|
|
434
|
+
function showSuccess() {
|
|
435
|
+
toast.add({ severity: 'success', summary: 'Saved', detail: 'Record updated', life: 3000 })
|
|
436
|
+
}
|
|
437
|
+
function showError() {
|
|
438
|
+
toast.add({ severity: 'error', summary: 'Error', detail: 'Something went wrong', life: 5000 })
|
|
439
|
+
}
|
|
440
|
+
</script>
|
|
441
|
+
<template>
|
|
442
|
+
<Toast />
|
|
443
|
+
<Button label="Save" @click="showSuccess" />
|
|
444
|
+
</template>
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
Toast severity options: `success`, `info`, `warn`, `error`, `secondary`, `contrast`.
|
|
448
|
+
|
|
449
|
+
#### Popover (Overlay Panel)
|
|
450
|
+
```vue
|
|
451
|
+
<script setup>
|
|
452
|
+
import { ref } from 'vue'
|
|
453
|
+
import Popover from 'primevue/popover'
|
|
454
|
+
|
|
455
|
+
const op = ref()
|
|
456
|
+
function toggle(event: Event) {
|
|
457
|
+
op.value.toggle(event)
|
|
458
|
+
}
|
|
459
|
+
</script>
|
|
460
|
+
<template>
|
|
461
|
+
<Button icon="pi pi-info-circle" @click="toggle" />
|
|
462
|
+
<Popover ref="op">
|
|
463
|
+
<p>Additional information here.</p>
|
|
464
|
+
</Popover>
|
|
465
|
+
</template>
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
#### Drawer (Sidebar)
|
|
469
|
+
```vue
|
|
470
|
+
<Drawer v-model:visible="visible" header="Menu" position="left">
|
|
471
|
+
<Menu :model="menuItems" />
|
|
472
|
+
</Drawer>
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
Position options: `left`, `right`, `top`, `bottom`, `full`.
|
|
476
|
+
|
|
477
|
+
### Navigation
|
|
478
|
+
|
|
479
|
+
#### Menubar
|
|
480
|
+
```vue
|
|
481
|
+
<Menubar :model="items">
|
|
482
|
+
<template #start>
|
|
483
|
+
<img src="/logo.svg" alt="Logo" class="h-8" />
|
|
484
|
+
</template>
|
|
485
|
+
<template #end>
|
|
486
|
+
<Button icon="pi pi-user" rounded text />
|
|
487
|
+
</template>
|
|
488
|
+
</Menubar>
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
Menu item model:
|
|
492
|
+
```typescript
|
|
493
|
+
const items = ref([
|
|
494
|
+
{
|
|
495
|
+
label: 'Home',
|
|
496
|
+
icon: 'pi pi-home',
|
|
497
|
+
command: () => router.visit('/')
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
label: 'Products',
|
|
501
|
+
icon: 'pi pi-box',
|
|
502
|
+
items: [
|
|
503
|
+
{ label: 'List', icon: 'pi pi-list', command: () => router.visit('/products') },
|
|
504
|
+
{ label: 'Create', icon: 'pi pi-plus', command: () => router.visit('/products/create') },
|
|
505
|
+
]
|
|
506
|
+
},
|
|
507
|
+
])
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
#### TabMenu
|
|
511
|
+
```vue
|
|
512
|
+
<TabMenu :model="tabs" />
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
#### Breadcrumb
|
|
516
|
+
```vue
|
|
517
|
+
<Breadcrumb :home="{ icon: 'pi pi-home', to: '/' }" :model="breadcrumbs" />
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
```typescript
|
|
521
|
+
const breadcrumbs = ref([
|
|
522
|
+
{ label: 'Products', to: '/products' },
|
|
523
|
+
{ label: 'Edit' },
|
|
524
|
+
])
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
#### Steps
|
|
528
|
+
```vue
|
|
529
|
+
<Steps :model="steps" :activeStep="activeStep" />
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
```typescript
|
|
533
|
+
const steps = ref([
|
|
534
|
+
{ label: 'Details' },
|
|
535
|
+
{ label: 'Pricing' },
|
|
536
|
+
{ label: 'Confirm' },
|
|
537
|
+
])
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
#### ContextMenu
|
|
541
|
+
```vue
|
|
542
|
+
<script setup>
|
|
543
|
+
import { ref } from 'vue'
|
|
544
|
+
import ContextMenu from 'primevue/contextmenu'
|
|
545
|
+
|
|
546
|
+
const cm = ref()
|
|
547
|
+
const items = ref([
|
|
548
|
+
{ label: 'Edit', icon: 'pi pi-pencil' },
|
|
549
|
+
{ label: 'Delete', icon: 'pi pi-trash' },
|
|
550
|
+
])
|
|
551
|
+
</script>
|
|
552
|
+
<template>
|
|
553
|
+
<div @contextmenu="cm.show($event)">
|
|
554
|
+
Right-click here
|
|
555
|
+
</div>
|
|
556
|
+
<ContextMenu ref="cm" :model="items" />
|
|
557
|
+
</template>
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
#### MegaMenu
|
|
561
|
+
```vue
|
|
562
|
+
<MegaMenu :model="megaItems" orientation="horizontal" />
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
#### PanelMenu
|
|
566
|
+
```vue
|
|
567
|
+
<PanelMenu :model="items" />
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
#### TieredMenu
|
|
571
|
+
```vue
|
|
572
|
+
<TieredMenu :model="items" popup ref="menu" />
|
|
573
|
+
<Button label="Menu" @click="menu.toggle($event)" />
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### Button Variants
|
|
577
|
+
```vue
|
|
578
|
+
<Button label="Primary" />
|
|
579
|
+
<Button label="Secondary" severity="secondary" />
|
|
580
|
+
<Button label="Success" severity="success" />
|
|
581
|
+
<Button label="Info" severity="info" />
|
|
582
|
+
<Button label="Warning" severity="warning" />
|
|
583
|
+
<Button label="Danger" severity="danger" />
|
|
584
|
+
<Button label="Help" severity="help" />
|
|
585
|
+
<Button label="Contrast" severity="contrast" />
|
|
586
|
+
|
|
587
|
+
<!-- Variants -->
|
|
588
|
+
<Button label="Outlined" outlined />
|
|
589
|
+
<Button label="Text" text />
|
|
590
|
+
<Button label="Rounded" rounded />
|
|
591
|
+
<Button label="Raised" raised />
|
|
592
|
+
<Button icon="pi pi-check" />
|
|
593
|
+
<Button label="Loading" :loading="isLoading" />
|
|
594
|
+
|
|
595
|
+
<!-- Link button -->
|
|
596
|
+
<Button label="Link" link />
|
|
597
|
+
|
|
598
|
+
<!-- Icon-only -->
|
|
599
|
+
<Button icon="pi pi-check" rounded aria-label="Confirm" />
|
|
600
|
+
|
|
601
|
+
<!-- Sizes -->
|
|
602
|
+
<Button label="Small" size="small" />
|
|
603
|
+
<Button label="Large" size="large" />
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
#### SplitButton
|
|
607
|
+
```vue
|
|
608
|
+
<SplitButton label="Save" :model="splitItems" @click="save" />
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
```typescript
|
|
612
|
+
const splitItems = ref([
|
|
613
|
+
{ label: 'Save as Draft', command: () => saveDraft() },
|
|
614
|
+
{ label: 'Export', command: () => exportData() },
|
|
615
|
+
])
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
#### SpeedDial
|
|
619
|
+
```vue
|
|
620
|
+
<SpeedDial :model="dialItems" direction="up" />
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
### Layout Components
|
|
624
|
+
|
|
625
|
+
#### Card
|
|
626
|
+
```vue
|
|
627
|
+
<Card>
|
|
628
|
+
<template #title>Title</template>
|
|
629
|
+
<template #subtitle>Subtitle</template>
|
|
630
|
+
<template #content>
|
|
631
|
+
<p>Content here</p>
|
|
632
|
+
</template>
|
|
633
|
+
<template #footer>
|
|
634
|
+
<Button label="Save" />
|
|
635
|
+
<Button label="Cancel" severity="secondary" />
|
|
636
|
+
</template>
|
|
637
|
+
</Card>
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
#### Tabs
|
|
641
|
+
```vue
|
|
642
|
+
<Tabs value="0">
|
|
643
|
+
<TabList>
|
|
644
|
+
<Tab value="0">General</Tab>
|
|
645
|
+
<Tab value="1">Security</Tab>
|
|
646
|
+
<Tab value="2">Notifications</Tab>
|
|
647
|
+
</TabList>
|
|
648
|
+
<TabPanels>
|
|
649
|
+
<TabPanel value="0">General content</TabPanel>
|
|
650
|
+
<TabPanel value="1">Security content</TabPanel>
|
|
651
|
+
<TabPanel value="2">Notification content</TabPanel>
|
|
652
|
+
</TabPanels>
|
|
653
|
+
</Tabs>
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
#### Accordion
|
|
657
|
+
```vue
|
|
658
|
+
<Accordion>
|
|
659
|
+
<AccordionPanel value="0">
|
|
660
|
+
<AccordionHeader>Section 1</AccordionHeader>
|
|
661
|
+
<AccordionContent>Content 1</AccordionContent>
|
|
662
|
+
</AccordionPanel>
|
|
663
|
+
<AccordionPanel value="1">
|
|
664
|
+
<AccordionHeader>Section 2</AccordionHeader>
|
|
665
|
+
<AccordionContent>Content 2</AccordionContent>
|
|
666
|
+
</AccordionPanel>
|
|
667
|
+
</Accordion>
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
#### Fieldset
|
|
671
|
+
```vue
|
|
672
|
+
<Fieldset legend="Address" :toggleable="true">
|
|
673
|
+
<p>Content here</p>
|
|
674
|
+
</Fieldset>
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
#### Panel
|
|
678
|
+
```vue
|
|
679
|
+
<Panel header="Settings" :toggleable="true">
|
|
680
|
+
<p>Panel content</p>
|
|
681
|
+
</Panel>
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
#### Divider
|
|
685
|
+
```vue
|
|
686
|
+
<Divider />
|
|
687
|
+
<Divider layout="vertical" />
|
|
688
|
+
<Divider align="center">
|
|
689
|
+
<span class="text-sm text-surface-500">OR</span>
|
|
690
|
+
</Divider>
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
#### Splitter
|
|
694
|
+
```vue
|
|
695
|
+
<Splitter style="height: 300px">
|
|
696
|
+
<SplitterPanel :size="40">Left panel</SplitterPanel>
|
|
697
|
+
<SplitterPanel :size="60">Right panel</SplitterPanel>
|
|
698
|
+
</Splitter>
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
#### ScrollPanel
|
|
702
|
+
```vue
|
|
703
|
+
<ScrollPanel style="width: 100%; height: 300px">
|
|
704
|
+
<p>Scrollable content...</p>
|
|
705
|
+
</ScrollPanel>
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
### Message / Feedback
|
|
709
|
+
|
|
710
|
+
#### Message
|
|
711
|
+
```vue
|
|
712
|
+
<Message severity="success">Operation completed</Message>
|
|
713
|
+
<Message severity="info">Information message</Message>
|
|
714
|
+
<Message severity="warn">Warning message</Message>
|
|
715
|
+
<Message severity="error">Error occurred</Message>
|
|
716
|
+
<Message severity="secondary">Secondary message</Message>
|
|
717
|
+
<Message severity="contrast">Contrast message</Message>
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
#### InlineMessage
|
|
721
|
+
```vue
|
|
722
|
+
<InlineMessage severity="error">Username is required</InlineMessage>
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
#### Tag
|
|
726
|
+
```vue
|
|
727
|
+
<Tag value="Active" severity="success" />
|
|
728
|
+
<Tag value="Pending" severity="warn" />
|
|
729
|
+
<Tag value="Inactive" severity="danger" />
|
|
730
|
+
<Tag icon="pi pi-check" value="Approved" severity="success" rounded />
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
#### Badge
|
|
734
|
+
```vue
|
|
735
|
+
<Badge value="4" severity="danger" />
|
|
736
|
+
<Button label="Inbox" badge="3" badgeSeverity="danger" />
|
|
737
|
+
|
|
738
|
+
<!-- Overlay badge -->
|
|
739
|
+
<OverlayBadge value="2">
|
|
740
|
+
<i class="pi pi-bell" style="font-size: 1.5rem" />
|
|
741
|
+
</OverlayBadge>
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
#### ProgressBar
|
|
745
|
+
```vue
|
|
746
|
+
<ProgressBar :value="50" />
|
|
747
|
+
<ProgressBar mode="indeterminate" style="height: 6px" />
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
#### ProgressSpinner
|
|
751
|
+
```vue
|
|
752
|
+
<ProgressSpinner style="width: 50px; height: 50px" />
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
#### Skeleton
|
|
756
|
+
```vue
|
|
757
|
+
<Skeleton width="100%" height="2rem" class="mb-2" />
|
|
758
|
+
<Skeleton shape="circle" size="4rem" />
|
|
759
|
+
<Skeleton width="10rem" class="mb-2" />
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
#### Chip
|
|
763
|
+
```vue
|
|
764
|
+
<Chip label="Vue" icon="pi pi-check" />
|
|
765
|
+
<Chip label="Removable" removable @remove="onRemove" />
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
#### Avatar
|
|
769
|
+
```vue
|
|
770
|
+
<Avatar label="KH" shape="circle" />
|
|
771
|
+
<Avatar image="/user.jpg" shape="circle" size="large" />
|
|
772
|
+
|
|
773
|
+
<AvatarGroup>
|
|
774
|
+
<Avatar image="/user1.jpg" shape="circle" />
|
|
775
|
+
<Avatar image="/user2.jpg" shape="circle" />
|
|
776
|
+
<Avatar label="+3" shape="circle" />
|
|
777
|
+
</AvatarGroup>
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
#### BlockUI
|
|
781
|
+
```vue
|
|
782
|
+
<BlockUI :blocked="blocked">
|
|
783
|
+
<Panel header="Content">
|
|
784
|
+
<p>This will be blocked</p>
|
|
785
|
+
</Panel>
|
|
786
|
+
</BlockUI>
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
### Misc Components
|
|
790
|
+
|
|
791
|
+
#### Image
|
|
792
|
+
```vue
|
|
793
|
+
<Image src="/product.jpg" alt="Product" width="250" preview />
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
#### Galleria
|
|
797
|
+
```vue
|
|
798
|
+
<Galleria :value="images" :numVisible="5" containerStyle="max-width: 640px">
|
|
799
|
+
<template #item="{ item }">
|
|
800
|
+
<img :src="item.src" :alt="item.alt" style="width: 100%" />
|
|
801
|
+
</template>
|
|
802
|
+
<template #thumbnail="{ item }">
|
|
803
|
+
<img :src="item.thumbnailSrc" :alt="item.alt" />
|
|
804
|
+
</template>
|
|
805
|
+
</Galleria>
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
#### Carousel
|
|
809
|
+
```vue
|
|
810
|
+
<Carousel :value="products" :numVisible="3" :numScroll="1">
|
|
811
|
+
<template #item="{ data }">
|
|
812
|
+
<Card>
|
|
813
|
+
<template #title>{{ data.name }}</template>
|
|
814
|
+
<template #content>{{ data.description }}</template>
|
|
815
|
+
</Card>
|
|
816
|
+
</template>
|
|
817
|
+
</Carousel>
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
#### VirtualScroller
|
|
821
|
+
```vue
|
|
822
|
+
<VirtualScroller :items="largeList" :itemSize="50" style="height: 400px">
|
|
823
|
+
<template #item="{ item, options }">
|
|
824
|
+
<div :class="{ 'surface-hover': options.odd }" class="p-2">
|
|
825
|
+
{{ item.name }}
|
|
826
|
+
</div>
|
|
827
|
+
</template>
|
|
828
|
+
</VirtualScroller>
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
#### Tooltip (directive)
|
|
832
|
+
```vue
|
|
833
|
+
<Button v-tooltip="'Save changes'" label="Save" />
|
|
834
|
+
<Button v-tooltip.top="'Top tooltip'" label="Top" />
|
|
835
|
+
<Button v-tooltip.bottom="'Bottom'" label="Bottom" />
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
#### Ripple (directive)
|
|
839
|
+
```vue
|
|
840
|
+
<div v-ripple class="p-4 cursor-pointer">Click me</div>
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
## Theming with Tailwind CSS
|
|
844
|
+
|
|
845
|
+
PrimeVue 4 uses a design token system that integrates with Tailwind:
|
|
846
|
+
|
|
847
|
+
```typescript
|
|
848
|
+
app.use(PrimeVue, {
|
|
849
|
+
theme: {
|
|
850
|
+
preset: Aura,
|
|
851
|
+
options: {
|
|
852
|
+
prefix: 'p',
|
|
853
|
+
darkModeSelector: '.dark',
|
|
854
|
+
cssLayer: {
|
|
855
|
+
name: 'primevue',
|
|
856
|
+
order: 'tailwind-base, primevue, tailwind-utilities'
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
})
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
### Dark Mode
|
|
864
|
+
PrimeVue respects the `darkModeSelector` option. With `'.dark'`, toggle via:
|
|
865
|
+
```typescript
|
|
866
|
+
document.documentElement.classList.toggle('dark')
|
|
867
|
+
```
|
|
868
|
+
|
|
869
|
+
### Customizing Theme Tokens
|
|
870
|
+
```typescript
|
|
871
|
+
import { definePreset } from '@primeuix/themes'
|
|
872
|
+
import Aura from '@primeuix/themes/aura'
|
|
873
|
+
|
|
874
|
+
const MyPreset = definePreset(Aura, {
|
|
875
|
+
semantic: {
|
|
876
|
+
primary: {
|
|
877
|
+
50: '{indigo.50}',
|
|
878
|
+
100: '{indigo.100}',
|
|
879
|
+
200: '{indigo.200}',
|
|
880
|
+
300: '{indigo.300}',
|
|
881
|
+
400: '{indigo.400}',
|
|
882
|
+
500: '{indigo.500}',
|
|
883
|
+
600: '{indigo.600}',
|
|
884
|
+
700: '{indigo.700}',
|
|
885
|
+
800: '{indigo.800}',
|
|
886
|
+
900: '{indigo.900}',
|
|
887
|
+
950: '{indigo.950}',
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
})
|
|
891
|
+
|
|
892
|
+
app.use(PrimeVue, { theme: { preset: MyPreset } })
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
### Tailwind CSS Layer Configuration
|
|
896
|
+
Add to `tailwind.config.js` / CSS:
|
|
897
|
+
```css
|
|
898
|
+
@layer tailwind-base, primevue, tailwind-utilities;
|
|
899
|
+
|
|
900
|
+
@layer tailwind-base {
|
|
901
|
+
@tailwind base;
|
|
902
|
+
}
|
|
903
|
+
@layer tailwind-utilities {
|
|
904
|
+
@tailwind components;
|
|
905
|
+
@tailwind utilities;
|
|
906
|
+
}
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
## PrimeIcons
|
|
910
|
+
|
|
911
|
+
Common icons:
|
|
912
|
+
```vue
|
|
913
|
+
<i class="pi pi-check" />
|
|
914
|
+
<i class="pi pi-times" />
|
|
915
|
+
<i class="pi pi-search" />
|
|
916
|
+
<i class="pi pi-user" />
|
|
917
|
+
<i class="pi pi-cog" />
|
|
918
|
+
<i class="pi pi-home" />
|
|
919
|
+
<i class="pi pi-file" />
|
|
920
|
+
<i class="pi pi-trash" />
|
|
921
|
+
<i class="pi pi-pencil" />
|
|
922
|
+
<i class="pi pi-plus" />
|
|
923
|
+
<i class="pi pi-minus" />
|
|
924
|
+
<i class="pi pi-download" />
|
|
925
|
+
<i class="pi pi-upload" />
|
|
926
|
+
<i class="pi pi-eye" />
|
|
927
|
+
<i class="pi pi-eye-slash" />
|
|
928
|
+
<i class="pi pi-filter" />
|
|
929
|
+
<i class="pi pi-sort-alt" />
|
|
930
|
+
<i class="pi pi-refresh" />
|
|
931
|
+
<i class="pi pi-bell" />
|
|
932
|
+
<i class="pi pi-calendar" />
|
|
933
|
+
<i class="pi pi-chart-bar" />
|
|
934
|
+
<i class="pi pi-envelope" />
|
|
935
|
+
<i class="pi pi-exclamation-triangle" />
|
|
936
|
+
<i class="pi pi-info-circle" />
|
|
937
|
+
<i class="pi pi-spinner pi-spin" />
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
Spin animation: add `pi-spin` class to any icon.
|
|
941
|
+
|
|
942
|
+
## Common Patterns with Laravel Inertia
|
|
943
|
+
|
|
944
|
+
### CRUD Page with DataTable + Dialog
|
|
945
|
+
```vue
|
|
946
|
+
<script setup lang="ts">
|
|
947
|
+
import { ref } from 'vue'
|
|
948
|
+
import { useForm, router } from '@inertiajs/vue3'
|
|
949
|
+
import DataTable from 'primevue/datatable'
|
|
950
|
+
import Column from 'primevue/column'
|
|
951
|
+
import Button from 'primevue/button'
|
|
952
|
+
import Dialog from 'primevue/dialog'
|
|
953
|
+
import InputText from 'primevue/inputtext'
|
|
954
|
+
import InputNumber from 'primevue/inputnumber'
|
|
955
|
+
import Toast from 'primevue/toast'
|
|
956
|
+
import ConfirmDialog from 'primevue/confirmdialog'
|
|
957
|
+
import { useToast } from 'primevue/usetoast'
|
|
958
|
+
import { useConfirm } from 'primevue/useconfirm'
|
|
959
|
+
|
|
960
|
+
interface Product {
|
|
961
|
+
id: number
|
|
962
|
+
name: string
|
|
963
|
+
price: number
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
const props = defineProps<{ products: Product[] }>()
|
|
967
|
+
const toast = useToast()
|
|
968
|
+
const confirm = useConfirm()
|
|
969
|
+
|
|
970
|
+
const dialogVisible = ref(false)
|
|
971
|
+
const editMode = ref(false)
|
|
972
|
+
const form = useForm({ id: 0, name: '', price: 0 })
|
|
973
|
+
|
|
974
|
+
function openNew() {
|
|
975
|
+
form.reset()
|
|
976
|
+
editMode.value = false
|
|
977
|
+
dialogVisible.value = true
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
function openEdit(product: Product) {
|
|
981
|
+
form.id = product.id
|
|
982
|
+
form.name = product.name
|
|
983
|
+
form.price = product.price
|
|
984
|
+
editMode.value = true
|
|
985
|
+
dialogVisible.value = true
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
function save() {
|
|
989
|
+
if (editMode.value) {
|
|
990
|
+
form.put(route('products.update', form.id), {
|
|
991
|
+
onSuccess: () => {
|
|
992
|
+
dialogVisible.value = false
|
|
993
|
+
toast.add({ severity: 'success', summary: 'Updated', life: 3000 })
|
|
994
|
+
},
|
|
995
|
+
})
|
|
996
|
+
} else {
|
|
997
|
+
form.post(route('products.store'), {
|
|
998
|
+
onSuccess: () => {
|
|
999
|
+
dialogVisible.value = false
|
|
1000
|
+
toast.add({ severity: 'success', summary: 'Created', life: 3000 })
|
|
1001
|
+
},
|
|
1002
|
+
})
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
function destroy(product: Product) {
|
|
1007
|
+
confirm.require({
|
|
1008
|
+
message: `Delete ${product.name}?`,
|
|
1009
|
+
header: 'Confirm',
|
|
1010
|
+
acceptProps: { label: 'Delete', severity: 'danger' },
|
|
1011
|
+
rejectProps: { label: 'Cancel', severity: 'secondary' },
|
|
1012
|
+
accept: () => {
|
|
1013
|
+
router.delete(route('products.destroy', product.id), {
|
|
1014
|
+
onSuccess: () => {
|
|
1015
|
+
toast.add({ severity: 'success', summary: 'Deleted', life: 3000 })
|
|
1016
|
+
},
|
|
1017
|
+
})
|
|
1018
|
+
},
|
|
1019
|
+
})
|
|
1020
|
+
}
|
|
1021
|
+
</script>
|
|
1022
|
+
|
|
1023
|
+
<template>
|
|
1024
|
+
<Toast />
|
|
1025
|
+
<ConfirmDialog />
|
|
1026
|
+
|
|
1027
|
+
<div class="flex justify-between items-center mb-4">
|
|
1028
|
+
<h1 class="text-2xl font-bold">Products</h1>
|
|
1029
|
+
<Button label="New Product" icon="pi pi-plus" @click="openNew" />
|
|
1030
|
+
</div>
|
|
1031
|
+
|
|
1032
|
+
<DataTable :value="products" stripedRows paginator :rows="10"
|
|
1033
|
+
:rowsPerPageOptions="[5, 10, 20, 50]">
|
|
1034
|
+
<Column field="name" header="Name" sortable />
|
|
1035
|
+
<Column field="price" header="Price" sortable>
|
|
1036
|
+
<template #body="{ data }">
|
|
1037
|
+
${{ data.price.toFixed(2) }}
|
|
1038
|
+
</template>
|
|
1039
|
+
</Column>
|
|
1040
|
+
<Column header="Actions" style="width: 8rem">
|
|
1041
|
+
<template #body="{ data }">
|
|
1042
|
+
<Button icon="pi pi-pencil" text rounded severity="info" @click="openEdit(data)" />
|
|
1043
|
+
<Button icon="pi pi-trash" text rounded severity="danger" @click="destroy(data)" />
|
|
1044
|
+
</template>
|
|
1045
|
+
</Column>
|
|
1046
|
+
</DataTable>
|
|
1047
|
+
|
|
1048
|
+
<Dialog v-model:visible="dialogVisible"
|
|
1049
|
+
:header="editMode ? 'Edit Product' : 'New Product'"
|
|
1050
|
+
:style="{ width: '400px' }" modal>
|
|
1051
|
+
<div class="flex flex-col gap-4">
|
|
1052
|
+
<div>
|
|
1053
|
+
<label class="font-semibold block mb-1">Name</label>
|
|
1054
|
+
<InputText v-model="form.name" class="w-full" :invalid="!!form.errors.name" />
|
|
1055
|
+
<small v-if="form.errors.name" class="text-red-500">{{ form.errors.name }}</small>
|
|
1056
|
+
</div>
|
|
1057
|
+
<div>
|
|
1058
|
+
<label class="font-semibold block mb-1">Price</label>
|
|
1059
|
+
<InputNumber v-model="form.price" mode="currency" currency="USD" class="w-full"
|
|
1060
|
+
:invalid="!!form.errors.price" />
|
|
1061
|
+
<small v-if="form.errors.price" class="text-red-500">{{ form.errors.price }}</small>
|
|
1062
|
+
</div>
|
|
1063
|
+
</div>
|
|
1064
|
+
<template #footer>
|
|
1065
|
+
<Button label="Cancel" severity="secondary" @click="dialogVisible = false" />
|
|
1066
|
+
<Button label="Save" :loading="form.processing" @click="save" />
|
|
1067
|
+
</template>
|
|
1068
|
+
</Dialog>
|
|
1069
|
+
</template>
|
|
1070
|
+
```
|
|
1071
|
+
|
|
1072
|
+
### Layout with Menubar + Sidebar
|
|
1073
|
+
```vue
|
|
1074
|
+
<script setup lang="ts">
|
|
1075
|
+
import { ref } from 'vue'
|
|
1076
|
+
import { router, usePage } from '@inertiajs/vue3'
|
|
1077
|
+
import Menubar from 'primevue/menubar'
|
|
1078
|
+
import Drawer from 'primevue/drawer'
|
|
1079
|
+
import Menu from 'primevue/menu'
|
|
1080
|
+
import Button from 'primevue/button'
|
|
1081
|
+
import Avatar from 'primevue/avatar'
|
|
1082
|
+
|
|
1083
|
+
const sidebarVisible = ref(false)
|
|
1084
|
+
const page = usePage()
|
|
1085
|
+
|
|
1086
|
+
const menuItems = ref([
|
|
1087
|
+
{ label: 'Dashboard', icon: 'pi pi-home', command: () => router.visit('/dashboard') },
|
|
1088
|
+
{ label: 'Products', icon: 'pi pi-box', command: () => router.visit('/products') },
|
|
1089
|
+
{ label: 'Orders', icon: 'pi pi-shopping-cart', command: () => router.visit('/orders') },
|
|
1090
|
+
{ label: 'Customers', icon: 'pi pi-users', command: () => router.visit('/customers') },
|
|
1091
|
+
{ separator: true },
|
|
1092
|
+
{ label: 'Settings', icon: 'pi pi-cog', command: () => router.visit('/settings') },
|
|
1093
|
+
])
|
|
1094
|
+
|
|
1095
|
+
const navItems = ref([
|
|
1096
|
+
{ label: 'Home', icon: 'pi pi-home', command: () => router.visit('/') },
|
|
1097
|
+
{
|
|
1098
|
+
label: 'Products',
|
|
1099
|
+
icon: 'pi pi-box',
|
|
1100
|
+
items: [
|
|
1101
|
+
{ label: 'All Products', command: () => router.visit('/products') },
|
|
1102
|
+
{ label: 'Categories', command: () => router.visit('/categories') },
|
|
1103
|
+
]
|
|
1104
|
+
},
|
|
1105
|
+
])
|
|
1106
|
+
</script>
|
|
1107
|
+
|
|
1108
|
+
<template>
|
|
1109
|
+
<Menubar :model="navItems">
|
|
1110
|
+
<template #start>
|
|
1111
|
+
<Button icon="pi pi-bars" text rounded @click="sidebarVisible = true" class="mr-2" />
|
|
1112
|
+
<span class="font-bold text-lg">MyApp</span>
|
|
1113
|
+
</template>
|
|
1114
|
+
<template #end>
|
|
1115
|
+
<Avatar :label="page.props.auth.user.name[0]" shape="circle" />
|
|
1116
|
+
</template>
|
|
1117
|
+
</Menubar>
|
|
1118
|
+
|
|
1119
|
+
<Drawer v-model:visible="sidebarVisible" header="Navigation">
|
|
1120
|
+
<Menu :model="menuItems" class="w-full border-0" />
|
|
1121
|
+
</Drawer>
|
|
1122
|
+
|
|
1123
|
+
<main class="p-4">
|
|
1124
|
+
<slot />
|
|
1125
|
+
</main>
|
|
1126
|
+
</template>
|
|
1127
|
+
```
|
|
1128
|
+
|
|
1129
|
+
### Form Page with Validation Errors
|
|
1130
|
+
```vue
|
|
1131
|
+
<script setup lang="ts">
|
|
1132
|
+
import { useForm } from '@inertiajs/vue3'
|
|
1133
|
+
import InputText from 'primevue/inputtext'
|
|
1134
|
+
import Select from 'primevue/select'
|
|
1135
|
+
import Textarea from 'primevue/textarea'
|
|
1136
|
+
import DatePicker from 'primevue/datepicker'
|
|
1137
|
+
import Button from 'primevue/button'
|
|
1138
|
+
|
|
1139
|
+
const props = defineProps<{ categories: { id: number; name: string }[] }>()
|
|
1140
|
+
|
|
1141
|
+
const form = useForm({
|
|
1142
|
+
name: '',
|
|
1143
|
+
category_id: null as number | null,
|
|
1144
|
+
description: '',
|
|
1145
|
+
published_at: null as Date | null,
|
|
1146
|
+
})
|
|
1147
|
+
|
|
1148
|
+
function submit() {
|
|
1149
|
+
form.post(route('articles.store'))
|
|
1150
|
+
}
|
|
1151
|
+
</script>
|
|
1152
|
+
|
|
1153
|
+
<template>
|
|
1154
|
+
<form @submit.prevent="submit" class="max-w-lg flex flex-col gap-4">
|
|
1155
|
+
<div>
|
|
1156
|
+
<label class="font-semibold block mb-1">Name</label>
|
|
1157
|
+
<InputText v-model="form.name" class="w-full" :invalid="!!form.errors.name" />
|
|
1158
|
+
<small v-if="form.errors.name" class="text-red-500">{{ form.errors.name }}</small>
|
|
1159
|
+
</div>
|
|
1160
|
+
|
|
1161
|
+
<div>
|
|
1162
|
+
<label class="font-semibold block mb-1">Category</label>
|
|
1163
|
+
<Select v-model="form.category_id" :options="categories"
|
|
1164
|
+
optionLabel="name" optionValue="id"
|
|
1165
|
+
placeholder="Select category" class="w-full"
|
|
1166
|
+
:invalid="!!form.errors.category_id" />
|
|
1167
|
+
<small v-if="form.errors.category_id" class="text-red-500">{{ form.errors.category_id }}</small>
|
|
1168
|
+
</div>
|
|
1169
|
+
|
|
1170
|
+
<div>
|
|
1171
|
+
<label class="font-semibold block mb-1">Description</label>
|
|
1172
|
+
<Textarea v-model="form.description" rows="4" class="w-full" autoResize />
|
|
1173
|
+
</div>
|
|
1174
|
+
|
|
1175
|
+
<div>
|
|
1176
|
+
<label class="font-semibold block mb-1">Publish Date</label>
|
|
1177
|
+
<DatePicker v-model="form.published_at" dateFormat="yy-mm-dd" showIcon class="w-full" />
|
|
1178
|
+
</div>
|
|
1179
|
+
|
|
1180
|
+
<div class="flex gap-2">
|
|
1181
|
+
<Button type="submit" label="Save" :loading="form.processing" />
|
|
1182
|
+
<Button type="button" label="Reset" severity="secondary" @click="form.reset()" />
|
|
1183
|
+
</div>
|
|
1184
|
+
</form>
|
|
1185
|
+
</template>
|
|
1186
|
+
```
|
|
1187
|
+
|
|
1188
|
+
## Common Gotchas
|
|
1189
|
+
|
|
1190
|
+
1. **Import each component individually** -- no global registration. Use `import Button from 'primevue/button'`, not from a barrel export.
|
|
1191
|
+
2. **Services need plugin registration** -- Toast and ConfirmDialog require `app.use(ToastService)` and `app.use(ConfirmationService)` in app.ts before use.
|
|
1192
|
+
3. **CSS layer order matters with Tailwind** -- Set `cssLayer` in theme config so Tailwind utilities can override PrimeVue styles. Without it, PrimeVue styles may override Tailwind classes.
|
|
1193
|
+
4. **v-model naming** -- Some components use named v-model: `v-model:visible` (Dialog, Drawer), `v-model:selection` (DataTable), `v-model:expandedRows` (DataTable), `v-model:filters` (DataTable), `v-model:selectionKeys` (Tree).
|
|
1194
|
+
5. **DataTable needs `dataKey`** -- Always set `dataKey="id"` when using selection, expandable rows, or row reordering.
|
|
1195
|
+
6. **Theme presets are imported from `@primeuix/themes`** -- not from `primevue`. The old `primevue/themes/*` path no longer works in v4.
|
|
1196
|
+
7. **PrimeIcons** -- Install separately: `npm install primeicons`. Import in app.ts: `import 'primeicons/primeicons.css'`.
|
|
1197
|
+
8. **Component rename in v4** -- `Dropdown` is now `Select`. `Calendar` is now `DatePicker`. `OverlayPanel` is now `Popover`. `Sidebar` is now `Drawer`. `InputSwitch` is now `ToggleSwitch`. `TabView`/`TabPanel` are now `Tabs`/`TabList`/`Tab`/`TabPanels`/`TabPanel`. `AccordionTab` is now `AccordionPanel`/`AccordionHeader`/`AccordionContent`.
|
|
1198
|
+
9. **FilterMatchMode import** -- In v4, import from `@primevue/core/api`, not from `primevue/api`.
|
|
1199
|
+
10. **No global CSS import** -- PrimeVue 4 does not require `import 'primevue/resources/themes/...'` or `import 'primevue/resources/primevue.min.css'`. The theme preset handles everything.
|