diff --git a/apps/showcase/doc/dataview/BasicDoc.vue b/apps/showcase/doc/dataview/BasicDoc.vue index 607c84386b..cadfbde295 100644 --- a/apps/showcase/doc/dataview/BasicDoc.vue +++ b/apps/showcase/doc/dataview/BasicDoc.vue @@ -7,9 +7,9 @@ <template #list="slotProps"> <div class="flex flex-col"> <div v-for="(item, index) in slotProps.items" :key="index"> - <div class="flex flex-col sm:flex-row sm:items-center p-6 gap-4" :class="{ 'border-t border-surface-200 dark:border-surface-700': index !== 0 }"> + <div class="flex flex-col sm:flex-row sm:items-center gap-4"> <div class="md:w-40 relative"> - <img class="block xl:block mx-auto rounded w-full" :src="`https://primefaces.org/cdn/primevue/images/product/${item.image}`" :alt="item.name" /> + <img class="rounded w-36" :src="`https://primefaces.org/cdn/primevue/images/product/${item.image}`" :alt="item.name" /> <div class="dark:bg-surface-900 absolute rounded-border" style="left: 4px; top: 4px"> <Tag :value="item.inventoryStatus" :severity="getSeverity(item)"></Tag> </div> diff --git a/apps/showcase/doc/fileupload/CustomUploadDoc.vue b/apps/showcase/doc/fileupload/CustomUploadDoc.vue index 6c79423cad..001ff8025f 100644 --- a/apps/showcase/doc/fileupload/CustomUploadDoc.vue +++ b/apps/showcase/doc/fileupload/CustomUploadDoc.vue @@ -1,9 +1,10 @@ <template> <DocSectionText v-bind="$attrs"> - <p>Uploading implementation can be overridden by enabling <i>customUpload</i> property and defining a custom <i>uploader</i> handler event.</p> + <p>Uploading implementation can be overridden by enabling <i>customUpload</i> property. This sample, displays the image on the client side with a grayscale filter.</p> </DocSectionText> - <div class="card flex justify-center"> - <FileUpload mode="basic" name="demo[]" url="/api/upload" accept="image/*" customUpload @uploader="customBase64Uploader" /> + <div class="card flex flex-col items-center gap-6"> + <FileUpload mode="basic" @select="onFileSelect" customUpload auto severity="secondary" class="p-button-outlined" /> + <img v-if="src" :src="src" alt="Image" class="shadow-md rounded-xl w-full sm:w-64" style="filter: grayscale(100%)" /> </div> <DocSectionCode :code="code" /> </template> @@ -12,30 +13,37 @@ export default { data() { return { + src: null, code: { basic: ` -<FileUpload mode="basic" name="demo[]" url="/api/upload" accept="image/*" customUpload @uploader="customBase64Uploader" /> +<FileUpload mode="basic" @select="onFileSelect" customUpload auto severity="secondary" class="p-button-outlined" /> +<img v-if="src" :src="src" alt="Image" class="shadow-md rounded-xl w-full sm:w-64" style="filter: grayscale(100%)" /> `, options: ` <template> - <div class="card flex justify-center"> - <FileUpload mode="basic" name="demo[]" url="/api/upload" accept="image/*" customUpload @uploader="customBase64Uploader" /> + <div class="card flex flex-col items-center gap-6"> + <FileUpload mode="basic" @select="onFileSelect" customUpload auto severity="secondary" class="p-button-outlined" /> + <img v-if="src" :src="src" alt="Image" class="shadow-md rounded-xl w-full sm:w-64" style="filter: grayscale(100%)" /> </div> </template> <script> export default { + data() { + return { + src: null + } + }, methods: { - async customBase64Uploader(event) { + onFileSelect(event) { const file = event.files[0]; const reader = new FileReader(); - let blob = await fetch(file.objectURL).then((r) => r.blob()); //blob:url - reader.readAsDataURL(blob); - - reader.onloadend = function () { - const base64data = reader.result; + reader.onload = async (e) => { + this.src = e.target.result; }; + + reader.readAsDataURL(file); } } }; @@ -43,39 +51,42 @@ export default { `, composition: ` <template> - <div class="card flex justify-center"> - <FileUpload mode="basic" name="demo[]" url="/api/upload" accept="image/*" customUpload @uploader="customBase64Uploader" /> + <div class="card flex flex-col items-center gap-6"> + <FileUpload mode="basic" @select="onFileSelect" customUpload auto severity="secondary" class="p-button-outlined" /> + <img v-if="src" :src="src" alt="Image" class="shadow-md rounded-xl w-full sm:w-64" style="filter: grayscale(100%)" /> </div> </template> <script setup> -const customBase64Uploader = async (event) => { +import { ref } from "vue"; + +const src = ref(null); + +function onFileSelect(event) { const file = event.files[0]; const reader = new FileReader(); - let blob = await fetch(file.objectURL).then((r) => r.blob()); //blob:url - - reader.readAsDataURL(blob); - reader.onloadend = function () { - const base64data = reader.result; + reader.onload = async (e) => { + src.value = e.target.result; }; -}; + + reader.readAsDataURL(file); +} <\/script> ` } }; }, methods: { - async customBase64Uploader(event) { + onFileSelect(event) { const file = event.files[0]; const reader = new FileReader(); - let blob = await fetch(file.objectURL).then((r) => r.blob()); //blob:url - - reader.readAsDataURL(blob); - reader.onloadend = function () { - const base64data = reader.result; + reader.onload = async (e) => { + this.src = e.target.result; }; + + reader.readAsDataURL(file); } } }; diff --git a/apps/showcase/doc/popover/DataTableDoc.vue b/apps/showcase/doc/popover/DataTableDoc.vue index 515e651fbf..d76dd51d60 100644 --- a/apps/showcase/doc/popover/DataTableDoc.vue +++ b/apps/showcase/doc/popover/DataTableDoc.vue @@ -1,34 +1,55 @@ <template> <DocSectionText v-bind="$attrs"> - <p>An example that displays a DataTable inside a popup to select an item.</p> + <p>Place the Popover outside of the data iteration components to avoid rendering it multiple times.</p> </DocSectionText> - <div class="card flex flex-col items-center gap-4"> - <Button type="button" icon="pi pi-search" :label="selectedProduct ? selectedProduct.name : 'Select a Product'" @click="toggle" aria-haspopup="true" aria-controls="overlay_panel" /> + <div class="card"> + <DataTable :value="products" :rows="5" paginator tableStyle="min-width: 50rem"> + <Column field="id" header="Id" class="w-1/6"></Column> + <Column field="code" header="Code" class="w-1/6"></Column> + <Column field="name" header="Name" class="w-1/6" bodyClass="whitespace-nowrap"></Column> + <Column field="price" header="Price" sortable class="w-1/6"> + <template #body="slotProps"> $ {{ slotProps.data.price }} </template> + </Column> + <Column header="Image" class="w-1/6"> + <template #body="slotProps"> + <img :src="`https://primefaces.org/cdn/primevue/images/product/${slotProps.data.image}`" :alt="slotProps.data.image" class="w-16 shadow-sm" /> + </template> + </Column> + <Column header="Details" class="w-1/6"> + <template #body="slotProps"> + <Button type="button" @click="displayProduct($event, slotProps.data)" icon="pi pi-search" severity="secondary" rounded></Button> + </template> + </Column> + </DataTable> - <div v-if="selectedProduct" class="p-8 bg-surface-0 dark:bg-surface-900 rounded border border-surface-200 dark:border-surface-700"> - <div class="relative"> - <img :src="`https://primefaces.org/cdn/primevue/images/product/${selectedProduct.image}`" :alt="selectedProduct.name" class="w-full sm:w-80" /> + <Popover ref="op"> + <div v-if="selectedProduct" class="rounded flex flex-col"> + <div class="flex justify-center rounded"> + <div class="relative mx-auto"> + <img class="rounded w-44 sm:w-64" :src="`https://primefaces.org/cdn/primevue/images/product/${selectedProduct.image}`" :alt="selectedProduct.name" /> + <Tag :value="selectedProduct.inventoryStatus" :severity="getSeverity(selectedProduct)" class="absolute dark:!bg-surface-900" style="left: 4px; top: 4px"></Tag> + </div> + </div> + <div class="pt-4"> + <div class="flex flex-row justify-between items-start gap-2 mb-4"> + <div> + <span class="font-medium text-surface-500 dark:text-surface-400 text-sm">{{ selectedProduct.category }}</span> + <div class="text-lg font-medium mt-1">{{ selectedProduct.name }}</div> + </div> + <div class="bg-surface-100 p-1" style="border-radius: 30px"> + <div class="bg-surface-0 flex items-center gap-2 justify-center py-1 px-2" style="border-radius: 30px; box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.04), 0px 1px 2px 0px rgba(0, 0, 0, 0.06)"> + <span class="text-surface-900 font-medium text-sm">{{ selectedProduct.rating }}</span> + <i class="pi pi-star-fill text-yellow-500"></i> + </div> + </div> + </div> + <div class="flex gap-2"> + <Button icon="pi pi-shopping-cart" :label="`Buy Now | \$${selectedProduct.price}`" :disabled="selectedProduct.inventoryStatus === 'OUTOFSTOCK'" class="flex-auto whitespace-nowrap" @click="hidePopover"></Button> + <Button icon="pi pi-heart" outlined @click="hidePopover"></Button> + </div> + </div> </div> - <div class="flex items-center justify-between mt-4 mb-2"> - <span class="font-semibold text-xl">{{ selectedProduct.name }}</span> - <span class="text-xl ml-4">{{ '$' + selectedProduct.price }}</span> - </div> - <span class="text-surface-500 dark:text-surface-400">{{ selectedProduct.category }}</span> - </div> - - <Popover ref="op" appendTo="body"> - <DataTable v-model:selection="selectedProduct" :value="products" selectionMode="single" :paginator="true" :rows="5" @row-select="onProductSelect"> - <Column field="name" header="Name" sortable style="min-width: 12rem"></Column> - <Column header="Image"> - <template #body="slotProps"> - <img :src="`https://primefaces.org/cdn/primevue/images/product/${slotProps.data.image}`" :alt="slotProps.data.image" class="w-16 shadow-sm" /> - </template> - </Column> - <Column field="price" header="Price" sortable style="min-width: 8rem"> - <template #body="slotProps"> $ {{ slotProps.data.price }} </template> - </Column> - </DataTable> </Popover> </div> <DocSectionCode :code="code" :service="['ProductService']" /> @@ -43,67 +64,103 @@ export default { selectedProduct: null, code: { basic: ` -<Toast /> -<Button type="button" icon="pi pi-search" :label="selectedProduct ? selectedProduct.name : 'Select a Product'" @click="toggle" aria-haspopup="true" aria-controls="overlay_panel" /> +<DataTable :value="products" :rows="5" paginator tableStyle="min-width: 50rem"> + <Column field="id" header="Id" class="w-1/6"></Column> + <Column field="code" header="Code" class="w-1/6"></Column> + <Column field="name" header="Name" class="w-1/6" bodyClass="whitespace-nowrap"></Column> + <Column field="price" header="Price" sortable class="w-1/6"> + <template #body="slotProps"> $ {{ slotProps.data.price }} </template> + </Column> + <Column header="Image" class="w-1/6"> + <template #body="slotProps"> + <img :src="\`https://primefaces.org/cdn/primevue/images/product/\${slotProps.data.image}\`" :alt="slotProps.data.image" class="w-16 shadow-sm" /> + </template> + </Column> + <Column header="Details" class="w-1/6"> + <template #body="slotProps"> + <Button type="button" @click="displayProduct($event, slotProps.data)" icon="pi pi-search" severity="secondary" rounded></Button> + </template> + </Column> +</DataTable> -<div v-if="selectedProduct" class="p-8 bg-surface-0 dark:bg-surface-900 rounded border border-surface-200 dark:border-surface-700"> - <div class="relative"> - <img :src="\`/images/product/\${selectedProduct.image}\`" :alt="selectedProduct.name" class="w-16 shadow-sm" class="w-full sm:w-80" /> - </div> - <div class="flex items-center justify-between mt-4 mb-2"> - <span class="font-semibold text-xl">{{ selectedProduct.name }}</span> - <span class="text-xl ml-4">{{ '$' + selectedProduct.price }}</span> +<Popover ref="op"> + <div v-if="selectedProduct" class="rounded flex flex-col"> + <div class="flex justify-center rounded"> + <div class="relative mx-auto"> + <img class="rounded w-44 sm:w-64" :src="\`https://primefaces.org/cdn/primevue/images/product/\${selectedProduct.image}\`" :alt="selectedProduct.name" /> + <Tag :value="selectedProduct.inventoryStatus" :severity="getSeverity(selectedProduct)" class="absolute dark:!bg-surface-900" style="left: 4px; top: 4px"></Tag> + </div> + </div> + <div class="pt-4"> + <div class="flex flex-row justify-between items-start gap-2 mb-4"> + <div> + <span class="font-medium text-surface-500 dark:text-surface-400 text-sm">{{ selectedProduct.category }}</span> + <div class="text-lg font-medium mt-1">{{ selectedProduct.name }}</div> + </div> + <div class="bg-surface-100 p-1" style="border-radius: 30px"> + <div class="bg-surface-0 flex items-center gap-2 justify-center py-1 px-2" style="border-radius: 30px; box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.04), 0px 1px 2px 0px rgba(0, 0, 0, 0.06)"> + <span class="text-surface-900 font-medium text-sm">{{ selectedProduct.rating }}</span> + <i class="pi pi-star-fill text-yellow-500"></i> + </div> + </div> + </div> + <div class="flex gap-2"> + <Button icon="pi pi-shopping-cart" :label="\`Buy Now | \\$\${selectedProduct.price}\`" :disabled="selectedProduct.inventoryStatus === 'OUTOFSTOCK'" class="flex-auto whitespace-nowrap" @click="hidePopover"></Button> + <Button icon="pi pi-heart" outlined @click="hidePopover"></Button> + </div> + </div> </div> - <span class="text-surface-500 dark:text-surface-400">{{ selectedProduct.category }}</span> -</div> - -<Popover ref="op" appendTo="body"> - <DataTable v-model:selection="selectedProduct" :value="products" selectionMode="single" :paginator="true" :rows="5" @row-select="onProductSelect"> - <Column field="name" header="Name" sortable style="width: 50%"></Column> - <Column header="Image" style="width: 20%"> - <template #body="slotProps"> - <img :src="\`/images/product/\${selectedProduct.image}\`" :alt="slotProps.data.image" class="w-16 shadow-sm" /> - </template> - </Column> - <Column field="price" header="Price" sortable style="width: 30%"> - <template #body="slotProps"> - $ {{ slotProps.data.price }} - </template> - </Column> - </DataTable> </Popover> `, options: ` <template> - <div class="card flex flex-col items-center gap-4"> - <Toast /> - <Button type="button" icon="pi pi-search" :label="selectedProduct ? selectedProduct.name : 'Select a Product'" @click="toggle" aria-haspopup="true" aria-controls="overlay_panel" /> + <div class="card"> + <DataTable :value="products" :rows="5" paginator tableStyle="min-width: 50rem"> + <Column field="id" header="Id" class="w-1/6"></Column> + <Column field="code" header="Code" class="w-1/6"></Column> + <Column field="name" header="Name" class="w-1/6" bodyClass="whitespace-nowrap"></Column> + <Column field="price" header="Price" sortable class="w-1/6"> + <template #body="slotProps"> $ {{ slotProps.data.price }} </template> + </Column> + <Column header="Image" class="w-1/6"> + <template #body="slotProps"> + <img :src="\`https://primefaces.org/cdn/primevue/images/product/\${slotProps.data.image}\`" :alt="slotProps.data.image" class="w-16 shadow-sm" /> + </template> + </Column> + <Column header="Details" class="w-1/6"> + <template #body="slotProps"> + <Button type="button" @click="displayProduct($event, slotProps.data)" icon="pi pi-search" severity="secondary" rounded></Button> + </template> + </Column> + </DataTable> - <div v-if="selectedProduct" class="p-8 bg-surface-0 dark:bg-surface-900 rounded border border-surface-200 dark:border-surface-700"> - <div class="relative"> - <img :src="\`https://primefaces.org/cdn/primevue/images/product/\${selectedProduct.image}\`" :alt="selectedProduct.name" class="w-full sm:w-80" /> - </div> - <div class="flex items-center justify-between mt-4 mb-2"> - <span class="font-semibold text-xl">{{ selectedProduct.name }}</span> - <span class="text-xl ml-4">{{ '$' + selectedProduct.price }}</span> + <Popover ref="op"> + <div v-if="selectedProduct" class="rounded flex flex-col"> + <div class="flex justify-center rounded"> + <div class="relative mx-auto"> + <img class="rounded w-44 sm:w-64" :src="\`https://primefaces.org/cdn/primevue/images/product/\${selectedProduct.image}\`" :alt="selectedProduct.name" /> + <Tag :value="selectedProduct.inventoryStatus" :severity="getSeverity(selectedProduct)" class="absolute dark:!bg-surface-900" style="left: 4px; top: 4px"></Tag> + </div> + </div> + <div class="pt-4"> + <div class="flex flex-row justify-between items-start gap-2 mb-4"> + <div> + <span class="font-medium text-surface-500 dark:text-surface-400 text-sm">{{ selectedProduct.category }}</span> + <div class="text-lg font-medium mt-1">{{ selectedProduct.name }}</div> + </div> + <div class="bg-surface-100 p-1" style="border-radius: 30px"> + <div class="bg-surface-0 flex items-center gap-2 justify-center py-1 px-2" style="border-radius: 30px; box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.04), 0px 1px 2px 0px rgba(0, 0, 0, 0.06)"> + <span class="text-surface-900 font-medium text-sm">{{ selectedProduct.rating }}</span> + <i class="pi pi-star-fill text-yellow-500"></i> + </div> + </div> + </div> + <div class="flex gap-2"> + <Button icon="pi pi-shopping-cart" :label="\`Buy Now | \\$\${selectedProduct.price}\`" :disabled="selectedProduct.inventoryStatus === 'OUTOFSTOCK'" class="flex-auto whitespace-nowrap" @click="hidePopover"></Button> + <Button icon="pi pi-heart" outlined @click="hidePopover"></Button> + </div> + </div> </div> - <span class="text-surface-500 dark:text-surface-400">{{ selectedProduct.category }}</span> - </div> - - <Popover ref="op" appendTo="body"> - <DataTable v-model:selection="selectedProduct" :value="products" selectionMode="single" :paginator="true" :rows="5" @row-select="onProductSelect"> - <Column field="name" header="Name" sortable style="min-width: 12rem"></Column> - <Column header="Image"> - <template #body="slotProps"> - <img :src="\`https://primefaces.org/cdn/primevue/images/product/\${selectedProduct.image}\`" :alt="slotProps.data.image" class="w-16 shadow-sm" /> - </template> - </Column> - <Column field="price" header="Price" sortable style="min-width: 8rem"> - <template #body="slotProps"> - $ {{ slotProps.data.price }} - </template> - </Column> - </DataTable> </Popover> </div> </template> @@ -119,17 +176,40 @@ export default { }; }, mounted() { - ProductService.getProductsSmall() - .then((data) => (this.products = data)) - .then(() => (this.selectedProduct = this.products[0])); + ProductService.getProductsSmall().then((data) => (this.products = data)); }, methods: { - toggle(event) { - this.$refs.op.toggle(event); + displayProduct(event, product) { + if (this.selectedProduct?.id === product.id) { + this.$refs.op.hide(); + this.selectedProduct = null; + } else { + this.selectedProduct = product; + this.$refs.op.show(event); + + // will not be needed with v4.0.6 as Popover will auto align + if (this.$refs.op.container) { + this.$refs.op.alignOverlay(); + } + } }, - onProductSelect(event) { + hidePopover() { this.$refs.op.hide(); - this.$toast.add({ severity: 'info', summary: 'Product Selected', detail: event.data.name, life: 3000 }); + }, + getSeverity(product) { + switch (product.inventoryStatus) { + case 'INSTOCK': + return 'success'; + + case 'LOWSTOCK': + return 'warn'; + + case 'OUTOFSTOCK': + return 'danger'; + + default: + return null; + } } } }; @@ -137,35 +217,53 @@ export default { `, composition: ` <template> - <div class="card flex flex-col items-center gap-4"> - <Toast /> - <Button type="button" icon="pi pi-search" :label="selectedProduct ? selectedProduct.name : 'Select a Product'" @click="toggle" aria-haspopup="true" aria-controls="overlay_panel" /> + <div class="card"> + <DataTable :value="products" :rows="5" paginator tableStyle="min-width: 50rem"> + <Column field="id" header="Id" class="w-1/6"></Column> + <Column field="code" header="Code" class="w-1/6"></Column> + <Column field="name" header="Name" class="w-1/6" bodyClass="whitespace-nowrap"></Column> + <Column field="price" header="Price" sortable class="w-1/6"> + <template #body="slotProps"> $ {{ slotProps.data.price }} </template> + </Column> + <Column header="Image" class="w-1/6"> + <template #body="slotProps"> + <img :src="\`https://primefaces.org/cdn/primevue/images/product/\${slotProps.data.image}\`" :alt="slotProps.data.image" class="w-16 shadow-sm" /> + </template> + </Column> + <Column header="Details" class="w-1/6"> + <template #body="slotProps"> + <Button type="button" @click="displayProduct($event, slotProps.data)" icon="pi pi-search" severity="secondary" rounded></Button> + </template> + </Column> + </DataTable> - <div v-if="selectedProduct" class="p-8 bg-surface-0 dark:bg-surface-900 rounded border border-surface-200 dark:border-surface-700"> - <div class="relative"> - <img :src="\`https://primefaces.org/cdn/primevue/images/product/\${selectedProduct.image}\`" :alt="selectedProduct.name" class="w-full sm:w-80" /> - </div> - <div class="flex items-center justify-between mt-4 mb-2"> - <span class="font-semibold text-xl">{{ selectedProduct.name }}</span> - <span class="text-xl ml-4">{{ '$' + selectedProduct.price }}</span> + <Popover ref="op"> + <div v-if="selectedProduct" class="rounded flex flex-col"> + <div class="flex justify-center rounded"> + <div class="relative mx-auto"> + <img class="rounded w-44 sm:w-64" :src="\`https://primefaces.org/cdn/primevue/images/product/\${selectedProduct.image}\`" :alt="selectedProduct.name" /> + <Tag :value="selectedProduct.inventoryStatus" :severity="getSeverity(selectedProduct)" class="absolute dark:!bg-surface-900" style="left: 4px; top: 4px"></Tag> + </div> + </div> + <div class="pt-4"> + <div class="flex flex-row justify-between items-start gap-2 mb-4"> + <div> + <span class="font-medium text-surface-500 dark:text-surface-400 text-sm">{{ selectedProduct.category }}</span> + <div class="text-lg font-medium mt-1">{{ selectedProduct.name }}</div> + </div> + <div class="bg-surface-100 p-1" style="border-radius: 30px"> + <div class="bg-surface-0 flex items-center gap-2 justify-center py-1 px-2" style="border-radius: 30px; box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.04), 0px 1px 2px 0px rgba(0, 0, 0, 0.06)"> + <span class="text-surface-900 font-medium text-sm">{{ selectedProduct.rating }}</span> + <i class="pi pi-star-fill text-yellow-500"></i> + </div> + </div> + </div> + <div class="flex gap-2"> + <Button icon="pi pi-shopping-cart" :label="\`Buy Now | \\$\${selectedProduct.price}\`" :disabled="selectedProduct.inventoryStatus === 'OUTOFSTOCK'" class="flex-auto whitespace-nowrap" @click="hidePopover"></Button> + <Button icon="pi pi-heart" outlined @click="hidePopover"></Button> + </div> + </div> </div> - <span class="text-surface-500 dark:text-surface-400">{{ selectedProduct.category }}</span> - </div> - - <Popover ref="op" appendTo="body"> - <DataTable v-model:selection="selectedProduct" :value="products" selectionMode="single" :paginator="true" :rows="5" @row-select="onProductSelect"> - <Column field="name" header="Name" sortable style="min-width: 12rem"></Column> - <Column header="Image"> - <template #body="slotProps"> - <img :src="\`https://primefaces.org/cdn/primevue/images/product/\${selectedProduct.image}\`" :alt="slotProps.data.image" class="w-16 shadow-sm" /> - </template> - </Column> - <Column field="price" header="Price" sortable style="min-width: 8rem"> - <template #body="slotProps"> - $ {{ slotProps.data.price }} - </template> - </Column> - </DataTable> </Popover> </div> </template> @@ -176,23 +274,46 @@ import { useToast } from "primevue/usetoast"; import { ProductService } from '@/service/ProductService'; onMounted(() => { - ProductService.getProductsSmall() - .then((data) => (products.value = data)) - .then(() => (selectedProduct.value = products.value[0])); + ProductService.getProductsSmall().then((data) => (products.value = data)); }); -const toast = useToast(); const op = ref(); const products = ref(); const selectedProduct = ref(); -const toggle = (event) => { - op.value.toggle(event); -}; +const displayProduct = (event, product) => { + if (selectedProduct.value?.id === product.id) { + op.value.hide(); + selectedProduct.value = null; + } else { + selectedProduct.value = product; + op.value.show(event); + + // will not be needed with v4.0.6 as Popover will auto align + if (op.value.container) { + op.value.alignOverlay(); + } + } +} -const onProductSelect = (event) => { +const hidePopover = () => { op.value.hide(); - toast.add({ severity: 'info', summary: 'Product Selected', detail: event.data.name, life: 3000 }); +} + +const getSeverity = (product) => { + switch (product.inventoryStatus) { + case 'INSTOCK': + return 'success'; + + case 'LOWSTOCK': + return 'warn'; + + case 'OUTOFSTOCK': + return 'danger'; + + default: + return null; + } } <\/script> `, @@ -216,17 +337,39 @@ const onProductSelect = (event) => { }; }, mounted() { - ProductService.getProductsSmall() - .then((data) => (this.products = data)) - .then(() => (this.selectedProduct = this.products[0])); + ProductService.getProductsSmall().then((data) => (this.products = data)); }, methods: { - toggle(event) { - this.$refs.op.toggle(event); + displayProduct(event, product) { + if (this.selectedProduct?.id === product.id) { + this.$refs.op.hide(); + this.selectedProduct = null; + } else { + this.selectedProduct = product; + this.$refs.op.show(event); + + if (this.$refs.op.container) { + this.$refs.op.alignOverlay(); + } + } }, - onProductSelect(event) { + hidePopover() { this.$refs.op.hide(); - this.$toast.add({ severity: 'info', summary: 'Product Selected', detail: event.data.name, life: 3000 }); + }, + getSeverity(product) { + switch (product.inventoryStatus) { + case 'INSTOCK': + return 'success'; + + case 'LOWSTOCK': + return 'warn'; + + case 'OUTOFSTOCK': + return 'danger'; + + default: + return null; + } } } }; diff --git a/apps/showcase/doc/popover/SelectDataDoc.vue b/apps/showcase/doc/popover/SelectDataDoc.vue new file mode 100644 index 0000000000..da73a51b40 --- /dev/null +++ b/apps/showcase/doc/popover/SelectDataDoc.vue @@ -0,0 +1,165 @@ +<template> + <DocSectionText v-bind="$attrs"> + <p>In this sample, data is retrieved from the content inside the popover.</p> + </DocSectionText> + <div class="card flex justify-center"> + <Button type="button" :label="selectedMember ? selectedMember.name : 'Select Member'" @click="toggle" class="min-w-48" /> + + <Popover ref="op"> + <div class="flex flex-col gap-4"> + <div> + <span class="font-medium block mb-2">Team Members</span> + <ul class="list-none p-0 m-0 flex flex-col"> + <li v-for="member in members" :key="member.name" class="flex items-center gap-2 px-2 py-3 hover:bg-emphasis cursor-pointer rounded-border" @click="selectMember(member)"> + <img :src="`https://primefaces.org/cdn/primevue/images/avatar/${member.image}`" style="width: 32px" /> + <div> + <span class="font-medium">{{ member.name }}</span> + <div class="text-sm text-surface-500 dark:text-surface-400">{{ member.email }}</div> + </div> + </li> + </ul> + </div> + </div> + </Popover> + </div> + <DocSectionCode :code="code" /> +</template> + +<script> +export default { + data() { + return { + selectedMember: null, + members: [ + { name: 'Amy Elsner', image: 'amyelsner.png', email: 'amy@email.com', role: 'Owner' }, + { name: 'Bernardo Dominic', image: 'bernardodominic.png', email: 'bernardo@email.com', role: 'Editor' }, + { name: 'Ioni Bowcher', image: 'ionibowcher.png', email: 'ioni@email.com', role: 'Viewer' } + ], + code: { + basic: ` +<Button type="button" :label="selectedMember ? selectedMember.name : 'Select Member'" @click="toggle" class="min-w-48" /> + +<Popover ref="op"> + <div class="flex flex-col gap-4"> + <div> + <span class="font-medium block mb-2">Team Members</span> + <ul class="list-none p-0 m-0 flex flex-col"> + <li v-for="member in members" :key="member.name" class="flex items-center gap-2 px-2 py-3 hover:bg-emphasis cursor-pointer rounded-border" @click="selectMember(member)"> + <img :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${member.image}\`" style="width: 32px" /> + <div> + <span class="font-medium">{{ member.name }}</span> + <div class="text-sm text-surface-500 dark:text-surface-400">{{ member.email }}</div> + </div> + </li> + </ul> + </div> + </div> +</Popover> +`, + options: ` +<template> + <div class="card flex justify-center"> + <Button type="button" :label="selectedMember ? selectedMember.name : 'Select Member'" @click="toggle" class="min-w-48" /> + + <Popover ref="op"> + <div class="flex flex-col gap-4"> + <div> + <span class="font-medium block mb-2">Team Members</span> + <ul class="list-none p-0 m-0 flex flex-col"> + <li v-for="member in members" :key="member.name" class="flex items-center gap-2 px-2 py-3 hover:bg-emphasis cursor-pointer rounded-border" @click="selectMember(member)"> + <img :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${member.image}\`" style="width: 32px" /> + <div> + <span class="font-medium">{{ member.name }}</span> + <div class="text-sm text-surface-500 dark:text-surface-400">{{ member.email }}</div> + </div> + </li> + </ul> + </div> + </div> + </Popover> + </div> +</template> + +<script> +export default { + data() { + return { + selectedMember: null, + members: [ + { name: 'Amy Elsner', image: 'amyelsner.png', email: 'amy@email.com', role: 'Owner' }, + { name: 'Bernardo Dominic', image: 'bernardodominic.png', email: 'bernardo@email.com', role: 'Editor' }, + { name: 'Ioni Bowcher', image: 'ionibowcher.png', email: 'ioni@email.com', role: 'Viewer' } + ] + } + }, + methods: { + toggle(event) { + this.$refs.op.toggle(event); + }, + selectMember(member) { + this.selectedMember = member; + this.$refs.op.hide(); + } + } +}; +<\/script> +`, + composition: ` +<template> + <div class="card flex justify-center"> + <Button type="button" :label="selectedMember ? selectedMember.name : 'Select Member'" @click="toggle" class="min-w-48" /> + + <Popover ref="op"> + <div class="flex flex-col gap-4"> + <div> + <span class="font-medium block mb-2">Team Members</span> + <ul class="list-none p-0 m-0 flex flex-col"> + <li v-for="member in members" :key="member.name" class="flex items-center gap-2 px-2 py-3 hover:bg-emphasis cursor-pointer rounded-border" @click="selectMember(member)"> + <img :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${member.image}\`" style="width: 32px" /> + <div> + <span class="font-medium">{{ member.name }}</span> + <div class="text-sm text-surface-500 dark:text-surface-400">{{ member.email }}</div> + </div> + </li> + </ul> + </div> + </div> + </Popover> + </div> +</template> + +<script setup> +import { ref } from "vue"; + +const op = ref(); +const selectedMember = ref(null); +const members = ref([ + { name: 'Amy Elsner', image: 'amyelsner.png', email: 'amy@email.com', role: 'Owner' }, + { name: 'Bernardo Dominic', image: 'bernardodominic.png', email: 'bernardo@email.com', role: 'Editor' }, + { name: 'Ioni Bowcher', image: 'ionibowcher.png', email: 'ioni@email.com', role: 'Viewer' } +]); + +const toggle = (event) => { + op.value.toggle(event); +} + +const selectMember = (member) => { + selectedMember.value = member; + op.value.hide(); +} +<\/script> +` + } + }; + }, + methods: { + toggle(event) { + this.$refs.op.toggle(event); + }, + selectMember(member) { + this.selectedMember = member; + this.$refs.op.hide(); + } + } +}; +</script> diff --git a/apps/showcase/pages/popover/index.vue b/apps/showcase/pages/popover/index.vue index cc43c9a927..c103f0c97b 100755 --- a/apps/showcase/pages/popover/index.vue +++ b/apps/showcase/pages/popover/index.vue @@ -16,6 +16,7 @@ import BasicDoc from '@/doc/popover/BasicDoc.vue'; import DataTableDoc from '@/doc/popover/DataTableDoc.vue'; import ImportDoc from '@/doc/popover/ImportDoc.vue'; import PTComponent from '@/doc/popover/pt/index.vue'; +import SelectDataDoc from '@/doc/popover/SelectDataDoc.vue'; import ThemingDoc from '@/doc/popover/theming/index.vue'; export default { @@ -32,6 +33,11 @@ export default { label: 'Basic', component: BasicDoc }, + { + id: 'selectdata', + label: 'Select Data', + component: SelectDataDoc + }, { id: 'datatable', label: 'DataTable', diff --git a/apps/showcase/pages/roadmap/index.vue b/apps/showcase/pages/roadmap/index.vue index e12a6f5526..c657f4f392 100644 --- a/apps/showcase/pages/roadmap/index.vue +++ b/apps/showcase/pages/roadmap/index.vue @@ -67,15 +67,24 @@ </div> <div class="flex-1 flex gap-4 flex-col"> <div class="p-4 bg-surface-0 dark:bg-surface-900 rounded border-blue-500 border-l-8"> - <h2 class="text-lg font-bold mt-0 mb-2">New Components</h2> - <p class="mt-0 mb-4 leading-normal">Layout, Typography, Tab orientations, Navigation Drawer.</p> + <h2 class="text-lg font-bold mt-0 mb-2">Form Library</h2> + <p class="mt-0 mb-4 leading-normal">Built-in form library with validations.</p> + <div class="bg-surface-200 rounded"> + <div class="bg-blue-500 rounded" style="width: 25%; height: 4px"></div> + </div> + </div> + </div> + <div class="flex-1 flex gap-4 flex-col"> + <div class="p-4 bg-surface-0 dark:bg-surface-900 rounded border-blue-500 border-l-8"> + <h2 class="text-lg font-bold mt-0 mb-2">Components</h2> + <p class="mt-0 mb-4 leading-normal">New Carousel, Tab Orientations, Updated Menu, Navigation Drawer...</p> <div class="bg-surface-200 rounded"> <div class="bg-blue-500 rounded" style="width: 0%; height: 4px"></div> </div> </div> <div class="p-4 bg-surface-0 dark:bg-surface-900 rounded border-blue-500 border-l-8"> - <h2 class="text-lg font-bold mt-0 mb-2">Form Library</h2> - <p class="mt-0 mb-4 leading-normal">Built-in form library with validations.</p> + <h2 class="text-lg font-bold mt-0 mb-2">Headless Mode</h2> + <p class="mt-0 mb-4 leading-normal">Headless component kit with Tailwind CSS.</p> <div class="bg-surface-200 rounded"> <div class="bg-blue-500 rounded" style="width: 0%; height: 4px"></div> </div> @@ -94,11 +103,9 @@ <div class="bg-blue-500 rounded" style="width: 0%; height: 4px"></div> </div> </div> - </div> - <div class="flex-1 flex gap-4 flex-col"> <div class="p-4 bg-surface-0 dark:bg-surface-900 rounded border-blue-500 border-l-8"> - <h2 class="text-lg font-bold mt-0 mb-2">Advanced Suite ... 2025</h2> - <p class="mt-0 mb-4 leading-normal">Gantt Chart, Flow Chart, Sheet, Calendar, Timeline, Editor.</p> + <h2 class="text-lg font-bold mt-0 mb-2">PrimeVue+ Suite | 2025</h2> + <p class="mt-0 mb-4 leading-normal">Gantt Chart, Flow Chart, Sheet, PDF Viewer, Calendar, Timeline, Editor.</p> <div class="bg-surface-200 rounded"> <div class="bg-blue-500 rounded" style="width: 0%; height: 4px"></div> </div> @@ -113,7 +120,7 @@ <h2 class="text-lg font-bold mt-0 mb-2">New Figma Tokens - Phase 1</h2> <p class="mt-0 mb-4 leading-normal">Update tokens to sync with the new styled mode.</p> <div class="bg-surface-200 rounded"> - <div class="bg-indigo-500 rounded" style="width: 50%; height: 4px"></div> + <div class="bg-indigo-500 rounded" style="width: 100%; height: 4px"></div> </div> </div> </div> @@ -122,9 +129,11 @@ <h2 class="text-lg font-bold mt-0 mb-2">New Figma Tokens - Phase 2</h2> <p class="mt-0 mb-4 leading-normal">Update tokens to sync with the new styled mode.</p> <div class="bg-surface-200 rounded"> - <div class="bg-indigo-500 rounded" style="width: 0%; height: 4px"></div> + <div class="bg-indigo-500 rounded" style="width: 50%; height: 4px"></div> </div> </div> + </div> + <div class="flex-1 flex gap-4 flex-col"> <div class="p-4 bg-surface-0 dark:bg-surface-900 rounded border-indigo-500 border-l-8"> <h2 class="text-lg font-bold mt-0 mb-2">Figma to Theme API</h2> <p class="mt-0 mb-4 leading-normal">Build a Figma plugin to generate themes from UI Kit.</p> @@ -133,7 +142,6 @@ </div> </div> </div> - <div class="flex-1 flex gap-4 flex-col"></div> </div> <div class="flex gap-4 border-b border-surface-200 dark:border-surface-700 pb-4"> <div class="shrink-0 p-4 bg-teal-500 text-white rounded font-bold text-lg flex items-center justify-center w-56">SHOWCASE</div> @@ -144,7 +152,7 @@ <h2 class="text-lg font-bold mt-0 mb-2">Documentation</h2> <p class="mt-0 mb-4 leading-normal">Interactive component viewer to explore tokens and pt sections.</p> <div class="bg-surface-200 rounded"> - <div class="bg-teal-500 rounded" style="width: 0%; height: 4px"></div> + <div class="bg-teal-500 rounded" style="width: 25%; height: 4px"></div> </div> </div> </div> @@ -159,37 +167,37 @@ <h2 class="text-lg font-bold mt-0 mb-2">V4 Update</h2> <p class="mt-0 mb-4 leading-normal">Update all templates to PrimeVue v4, replace PrimeFlex demos with Tailwind.</p> <div class="bg-surface-200 rounded"> - <div class="bg-teal-500 rounded" style="width: 0%; height: 4px"></div> + <div class="bg-violet-500 rounded" style="width: 100%; height: 4px"></div> </div> </div> + </div> + <div class="flex-1 flex gap-4 flex-col"> <div class="p-4 bg-surface-0 dark:bg-surface-900 rounded border-violet-500 border-l-8"> <h2 class="text-lg font-bold mt-0 mb-2">Genesis</h2> - <p class="mt-0 mb-4 leading-normal">Brand new template application.</p> + <p class="mt-0 mb-4 leading-normal">Brand new multi-purpose template.</p> <div class="bg-surface-200 rounded"> <div class="bg-teal-500 rounded" style="width: 0%; height: 4px"></div> </div> </div> </div> - <div class="flex-1 flex gap-4 flex-col"></div> </div> <div class="flex gap-4 border-b border-surface-200 dark:border-surface-700 pb-4"> <div class="shrink-0 p-4 bg-orange-500 text-white rounded font-bold text-lg flex items-center justify-center w-56">PrimeBlocks</div> <div class="flex-1 flex gap-4 flex-col"></div> + <div class="flex-1 flex gap-4 flex-col"></div> <div class="flex-1 flex gap-4 flex-col"> <div class="p-4 bg-surface-0 dark:bg-surface-900 rounded border-orange-500 border-l-8"> <h2 class="text-lg font-bold mt-0 mb-2">Tailwind Blocks</h2> - <p class="mt-0 mb-4 leading-normal">Migrate Blocks to Tailwind CSS.</p> + <p class="mt-0 mb-4 leading-normal">Migrate Blocks to v4 and Tailwind CSS.</p> <div class="bg-surface-200 rounded"> - <div class="bg-orange-500 rounded" style="width: 50%; height: 4px"></div> + <div class="bg-orange-500 rounded" style="width: 90%; height: 4px"></div> </div> </div> - </div> - <div class="flex-1 flex gap-4 flex-col"> <div class="p-4 bg-surface-0 dark:bg-surface-900 rounded border-orange-500 border-l-8"> <h2 class="text-lg font-bold mt-0 mb-2">Online App</h2> - <p class="mt-0 mb-4 leading-normal">Implement a SaaS app to access the blocks instead of an offline download.</p> + <p class="mt-0 mb-4 leading-normal">Implement an app to access the blocks instead of an offline download.</p> <div class="bg-surface-200 rounded"> - <div class="bg-orange-500 rounded" style="width: 0%; height: 4px"></div> + <div class="bg-orange-500 rounded" style="width: 90%; height: 4px"></div> </div> </div> </div> @@ -199,7 +207,7 @@ <div class="shrink-0 p-4 bg-pink-500 text-white rounded font-bold text-lg flex items-center justify-center w-56">Design</div> <div class="flex-1 flex gap-4 flex-col"> <div class="p-4 bg-surface-0 dark:bg-surface-900 rounded border-pink-500 border-l-8"> - <h2 class="text-lg font-bold mt-0 mb-2">New UI Theme</h2> + <h2 class="text-lg font-bold mt-0 mb-2">New Aura Theme</h2> <p class="mt-0 mb-4 leading-normal">Brand new default theme with a modern and attractive design.</p> <div class="bg-surface-200 rounded"> <div class="bg-pink-500 rounded" style="width: 100%; height: 4px"></div> @@ -207,6 +215,7 @@ </div> </div> <div class="flex-1 flex gap-4 flex-col"></div> + <div class="flex-1 flex gap-4 flex-col"></div> <div class="flex-1 flex gap-4 flex-col"> <div class="flex-1 flex gap-4 flex-col"> <div class="p-4 bg-surface-0 dark:bg-surface-900 rounded border-pink-500 border-l-8"> @@ -218,7 +227,6 @@ </div> </div> </div> - <div class="flex-1 flex gap-4 flex-col"></div> </div> </div> </div>