mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 06:29:30 -07:00
Demo Improvements and Masonry
This commit is contained in:
126
src/components/common/PreviewSelect.vue
Normal file
126
src/components/common/PreviewSelect.vue
Normal file
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<div class="preview-select">
|
||||
<span class="select-label">{{ title }}</span>
|
||||
<Select
|
||||
:model-value="modelValue"
|
||||
@update:model-value="handleSelectChange"
|
||||
:options="options"
|
||||
v-bind="selectAttributes"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
class="custom-select"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import Select from 'primevue/select'
|
||||
|
||||
interface Option {
|
||||
label: string
|
||||
value: string | number
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
title: string
|
||||
modelValue: string | number
|
||||
options: Option[] | string[] | number[]
|
||||
optionLabel?: string
|
||||
optionValue?: string
|
||||
placeholder?: string
|
||||
disabled?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: string | number]
|
||||
}>()
|
||||
|
||||
const handleSelectChange = (value: string | number) => {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
|
||||
const isObjectArray = computed(() => {
|
||||
return props.options.length > 0 && typeof props.options[0] === 'object'
|
||||
})
|
||||
|
||||
const selectAttributes = computed(() => {
|
||||
if (isObjectArray.value) {
|
||||
return {
|
||||
optionLabel: props.optionLabel || 'label',
|
||||
optionValue: props.optionValue || 'value'
|
||||
}
|
||||
}
|
||||
return {}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.preview-select {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
.select-label {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
white-space: nowrap;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.custom-select {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
:deep(.p-select) {
|
||||
background: #1a1a1a;
|
||||
border: 1px solid #333;
|
||||
border-radius: 6px;
|
||||
color: #fff;
|
||||
font-size: 0.875rem;
|
||||
min-height: 2.5rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:deep(.p-select:hover) {
|
||||
border-color: #555;
|
||||
}
|
||||
|
||||
:deep(.p-select:focus) {
|
||||
outline: none;
|
||||
border-color: #555;
|
||||
box-shadow: 0 0 0 2px rgba(82, 39, 255, 0.2);
|
||||
}
|
||||
|
||||
:deep(.p-select-label) {
|
||||
color: #fff;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
:deep(.p-select-dropdown) {
|
||||
background: #1a1a1a;
|
||||
border: none;
|
||||
color: #fff;
|
||||
width: 2.5rem;
|
||||
border-radius: 0 6px 6px 0;
|
||||
}
|
||||
|
||||
:deep(.p-select-dropdown:hover) {
|
||||
background: #333;
|
||||
}
|
||||
|
||||
:deep(.p-select-overlay) {
|
||||
background: #1a1a1a;
|
||||
border: 1px solid #333;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
:deep(.p-select-option) {
|
||||
color: #fff;
|
||||
padding: 0.5rem;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
</style>
|
||||
@@ -19,7 +19,7 @@ export const CATEGORIES = [
|
||||
{
|
||||
name: 'Components',
|
||||
subcategories: [
|
||||
|
||||
'Masonry',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ const textAnimations = {
|
||||
};
|
||||
|
||||
const components = {
|
||||
|
||||
'masonry': () => import("../demo/Components/MasonryDemo.vue"),
|
||||
};
|
||||
|
||||
const backgrounds = {
|
||||
|
||||
32
src/constants/code/Components/masonryCode.ts
Normal file
32
src/constants/code/Components/masonryCode.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import code from '@content/Components/Masonry/Masonry.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
|
||||
export const masonry: CodeObject = {
|
||||
cli: `npx jsrepo add https://vuebits.dev/Components/Masonry`,
|
||||
installation: `npm install gsap`,
|
||||
usage: `<template>
|
||||
<Masonry
|
||||
:items="items"
|
||||
:duration="0.6"
|
||||
:stagger="0.05"
|
||||
animate-from="bottom"
|
||||
:scale-on-hover="true"
|
||||
:hover-scale="0.95"
|
||||
:blur-to-focus="true"
|
||||
:color-shift-on-hover="false"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import Masonry from "./Masonry.vue"
|
||||
|
||||
const items = ref([
|
||||
{ id: '1', img: 'https://picsum.photos/300/400?random=1', url: 'https://picsum.photos', height: 400 },
|
||||
{ id: '2', img: 'https://picsum.photos/300/600?random=2', url: 'https://picsum.photos', height: 600 },
|
||||
{ id: '3', img: 'https://picsum.photos/300/350?random=3', url: 'https://picsum.photos', height: 350 },
|
||||
// ... more items
|
||||
])
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
261
src/content/Components/Masonry/Masonry.vue
Normal file
261
src/content/Components/Masonry/Masonry.vue
Normal file
@@ -0,0 +1,261 @@
|
||||
<template>
|
||||
<div ref="containerRef" class="relative w-full h-full">
|
||||
<div v-for="item in grid" :key="item.id" :data-key="item.id" class="absolute box-content"
|
||||
:style="{ willChange: 'transform, width, height, opacity' }" @click="openUrl(item.url)"
|
||||
@mouseenter="(e) => handleMouseEnter(item.id, e.currentTarget as HTMLElement)"
|
||||
@mouseleave="(e) => handleMouseLeave(item.id, e.currentTarget as HTMLElement)">
|
||||
<div
|
||||
class="relative w-full h-full bg-cover bg-center rounded-[10px] shadow-[0px_10px_50px_-10px_rgba(0,0,0,0.2)] uppercase text-[10px] leading-[10px]"
|
||||
:style="{ backgroundImage: `url(${item.img})` }">
|
||||
<div v-if="colorShiftOnHover"
|
||||
class="color-overlay absolute inset-0 rounded-[10px] bg-gradient-to-tr from-pink-500/50 to-sky-500/50 opacity-0 pointer-events-none" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted, watchEffect, nextTick } from 'vue'
|
||||
import { gsap } from 'gsap'
|
||||
|
||||
interface Item {
|
||||
id: string
|
||||
img: string
|
||||
url: string
|
||||
height: number
|
||||
}
|
||||
|
||||
interface MasonryProps {
|
||||
items: Item[]
|
||||
ease?: string
|
||||
duration?: number
|
||||
stagger?: number
|
||||
animateFrom?: 'bottom' | 'top' | 'left' | 'right' | 'center' | 'random'
|
||||
scaleOnHover?: boolean
|
||||
hoverScale?: number
|
||||
blurToFocus?: boolean
|
||||
colorShiftOnHover?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<MasonryProps>(), {
|
||||
ease: 'power3.out',
|
||||
duration: 0.6,
|
||||
stagger: 0.05,
|
||||
animateFrom: 'bottom',
|
||||
scaleOnHover: true,
|
||||
hoverScale: 0.95,
|
||||
blurToFocus: true,
|
||||
colorShiftOnHover: false
|
||||
})
|
||||
|
||||
const useMedia = (queries: string[], values: number[], defaultValue: number) => {
|
||||
const get = () => values[queries.findIndex((q) => matchMedia(q).matches)] ?? defaultValue
|
||||
const value = ref<number>(get())
|
||||
|
||||
onMounted(() => {
|
||||
const handler = () => value.value = get()
|
||||
queries.forEach((q) => matchMedia(q).addEventListener('change', handler))
|
||||
|
||||
onUnmounted(() => {
|
||||
queries.forEach((q) => matchMedia(q).removeEventListener('change', handler))
|
||||
})
|
||||
})
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
const useMeasure = () => {
|
||||
const containerRef = ref<HTMLDivElement | null>(null)
|
||||
const size = ref({ width: 0, height: 0 })
|
||||
let resizeObserver: ResizeObserver | null = null
|
||||
|
||||
onMounted(() => {
|
||||
if (!containerRef.value) return
|
||||
|
||||
resizeObserver = new ResizeObserver(([entry]) => {
|
||||
const { width, height } = entry.contentRect
|
||||
size.value = { width, height }
|
||||
})
|
||||
|
||||
resizeObserver.observe(containerRef.value)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (resizeObserver) {
|
||||
resizeObserver.disconnect()
|
||||
}
|
||||
})
|
||||
|
||||
return [containerRef, size] as const
|
||||
}
|
||||
|
||||
const preloadImages = async (urls: string[]): Promise<void> => {
|
||||
await Promise.all(
|
||||
urls.map(
|
||||
(src) =>
|
||||
new Promise<void>((resolve) => {
|
||||
const img = new Image()
|
||||
img.src = src
|
||||
img.onload = img.onerror = () => resolve()
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const columns = useMedia(
|
||||
[
|
||||
'(min-width:1500px)',
|
||||
'(min-width:1000px)',
|
||||
'(min-width:600px)',
|
||||
'(min-width:400px)'
|
||||
],
|
||||
[5, 4, 3, 2],
|
||||
1
|
||||
)
|
||||
|
||||
const [containerRef, size] = useMeasure()
|
||||
const imagesReady = ref(false)
|
||||
const hasMounted = ref(false)
|
||||
|
||||
const grid = computed(() => {
|
||||
if (!size.value.width) return []
|
||||
const colHeights = new Array(columns.value).fill(0)
|
||||
const gap = 16
|
||||
const totalGaps = (columns.value - 1) * gap
|
||||
const columnWidth = (size.value.width - totalGaps) / columns.value
|
||||
|
||||
return props.items.map((child) => {
|
||||
const col = colHeights.indexOf(Math.min(...colHeights))
|
||||
const x = col * (columnWidth + gap)
|
||||
const height = child.height / 2
|
||||
const y = colHeights[col]
|
||||
|
||||
colHeights[col] += height + gap
|
||||
return { ...child, x, y, w: columnWidth, h: height }
|
||||
})
|
||||
})
|
||||
|
||||
const openUrl = (url: string) => {
|
||||
window.open(url, '_blank', 'noopener')
|
||||
}
|
||||
|
||||
interface GridItem extends Item {
|
||||
x: number
|
||||
y: number
|
||||
w: number
|
||||
h: number
|
||||
}
|
||||
|
||||
const getInitialPosition = (item: GridItem) => {
|
||||
const containerRect = containerRef.value?.getBoundingClientRect()
|
||||
if (!containerRect) return { x: item.x, y: item.y }
|
||||
|
||||
let direction = props.animateFrom
|
||||
if (props.animateFrom === 'random') {
|
||||
const dirs = ['top', 'bottom', 'left', 'right']
|
||||
direction = dirs[Math.floor(Math.random() * dirs.length)] as typeof props.animateFrom
|
||||
}
|
||||
|
||||
switch (direction) {
|
||||
case 'top':
|
||||
return { x: item.x, y: -200 }
|
||||
case 'bottom':
|
||||
return { x: item.x, y: window.innerHeight + 200 }
|
||||
case 'left':
|
||||
return { x: -200, y: item.y }
|
||||
case 'right':
|
||||
return { x: window.innerWidth + 200, y: item.y }
|
||||
case 'center':
|
||||
return {
|
||||
x: containerRect.width / 2 - item.w / 2,
|
||||
y: containerRect.height / 2 - item.h / 2
|
||||
}
|
||||
default:
|
||||
return { x: item.x, y: item.y + 100 }
|
||||
}
|
||||
}
|
||||
|
||||
const handleMouseEnter = (id: string, element: HTMLElement) => {
|
||||
if (props.scaleOnHover) {
|
||||
gsap.to(`[data-key="${id}"]`, {
|
||||
scale: props.hoverScale,
|
||||
duration: 0.3,
|
||||
ease: 'power2.out'
|
||||
})
|
||||
}
|
||||
if (props.colorShiftOnHover) {
|
||||
const overlay = element.querySelector('.color-overlay') as HTMLElement
|
||||
if (overlay) gsap.to(overlay, { opacity: 0.3, duration: 0.3 })
|
||||
}
|
||||
}
|
||||
|
||||
const handleMouseLeave = (id: string, element: HTMLElement) => {
|
||||
if (props.scaleOnHover) {
|
||||
gsap.to(`[data-key="${id}"]`, {
|
||||
scale: 1,
|
||||
duration: 0.3,
|
||||
ease: 'power2.out'
|
||||
})
|
||||
}
|
||||
if (props.colorShiftOnHover) {
|
||||
const overlay = element.querySelector('.color-overlay') as HTMLElement
|
||||
if (overlay) gsap.to(overlay, { opacity: 0, duration: 0.3 })
|
||||
}
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
preloadImages(props.items.map((i) => i.img)).then(() => {
|
||||
imagesReady.value = true
|
||||
})
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
if (!imagesReady.value) return
|
||||
|
||||
const currentGrid = grid.value
|
||||
void props.items.length
|
||||
void columns.value
|
||||
void size.value.width
|
||||
|
||||
if (!currentGrid.length) return
|
||||
|
||||
nextTick(() => {
|
||||
currentGrid.forEach((item, index) => {
|
||||
const selector = `[data-key="${item.id}"]`
|
||||
const animProps = { x: item.x, y: item.y, width: item.w, height: item.h }
|
||||
|
||||
if (!hasMounted.value) {
|
||||
const start = getInitialPosition(item)
|
||||
gsap.fromTo(
|
||||
selector,
|
||||
{
|
||||
opacity: 0,
|
||||
x: start.x,
|
||||
y: start.y,
|
||||
width: item.w,
|
||||
height: item.h,
|
||||
...(props.blurToFocus && { filter: 'blur(10px)' })
|
||||
},
|
||||
{
|
||||
opacity: 1,
|
||||
...animProps,
|
||||
...(props.blurToFocus && { filter: 'blur(0px)' }),
|
||||
duration: 0.8,
|
||||
ease: 'power3.out',
|
||||
delay: index * props.stagger
|
||||
}
|
||||
)
|
||||
} else {
|
||||
gsap.to(selector, {
|
||||
...animProps,
|
||||
duration: props.duration,
|
||||
ease: props.ease,
|
||||
overwrite: 'auto'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
hasMounted.value = true
|
||||
})
|
||||
})
|
||||
</script>
|
||||
0
src/content/Components/TiltedCard/TiltedCard.vue
Normal file
0
src/content/Components/TiltedCard/TiltedCard.vue
Normal file
@@ -16,8 +16,8 @@
|
||||
class="fade-content-demo-content"
|
||||
>
|
||||
<div class="demo-content">
|
||||
<h4>Fade Content Example</h4>
|
||||
<p>This content will fade in when it enters the viewport.</p>
|
||||
<h4>Fade Content</h4>
|
||||
<p>It will fade in when it enters the viewport.</p>
|
||||
</div>
|
||||
</FadeContent>
|
||||
</div>
|
||||
|
||||
116
src/demo/Components/MasonryDemo.vue
Normal file
116
src/demo/Components/MasonryDemo.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<div class="masonry-demo">
|
||||
<TabbedLayout>
|
||||
<template #preview>
|
||||
<div class="demo-container" style="height: 900px; overflow: hidden;">
|
||||
<RefreshButton @refresh="forceRerender" />
|
||||
|
||||
<Masonry :key="rerenderKey" :items="sampleItems" :ease="ease" :duration="duration" :stagger="stagger"
|
||||
:animate-from="animateFrom" :scale-on-hover="scaleOnHover" :hover-scale="hoverScale"
|
||||
:blur-to-focus="blurToFocus" :color-shift-on-hover="colorShiftOnHover" class="masonry-demo-container" />
|
||||
</div>
|
||||
|
||||
<Customize>
|
||||
<PreviewSwitch title="Scale on Hover" v-model="scaleOnHover" @update:model-value="forceRerender" />
|
||||
|
||||
<PreviewSwitch title="Blur to Focus" v-model="blurToFocus" @update:model-value="forceRerender" />
|
||||
|
||||
<PreviewSwitch title="Color Shift on Hover" v-model="colorShiftOnHover" @update:model-value="forceRerender" />
|
||||
|
||||
<PreviewSelect title="Animation Direction" v-model="animateFrom" :options="[
|
||||
{ label: 'Bottom', value: 'bottom' },
|
||||
{ label: 'Top', value: 'top' },
|
||||
{ label: 'Left', value: 'left' },
|
||||
{ label: 'Right', value: 'right' },
|
||||
{ label: 'Center', value: 'center' },
|
||||
{ label: 'Random', value: 'random' }
|
||||
]" @update:model-value="forceRerender" />
|
||||
|
||||
<PreviewSlider title="Duration (s)" v-model="duration" :min="0.1" :max="2" :step="0.1"
|
||||
@update:model-value="forceRerender" />
|
||||
|
||||
<PreviewSlider title="Stagger Delay (s)" v-model="stagger" :min="0.01" :max="0.2" :step="0.01"
|
||||
@update:model-value="forceRerender" />
|
||||
|
||||
<PreviewSlider title="Hover Scale" v-model="hoverScale" :min="0.8" :max="1.2" :step="0.05"
|
||||
@update:model-value="forceRerender" />
|
||||
</Customize>
|
||||
|
||||
<PropTable :data="propData" />
|
||||
<Dependencies :dependency-list="['gsap']" />
|
||||
</template>
|
||||
|
||||
<template #code>
|
||||
<CodeExample :code-object="masonry" />
|
||||
</template>
|
||||
|
||||
<template #cli>
|
||||
<CliInstallation :command="masonry.cli" />
|
||||
</template>
|
||||
</TabbedLayout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import TabbedLayout from '../../components/common/TabbedLayout.vue'
|
||||
import RefreshButton from '../../components/common/RefreshButton.vue'
|
||||
import PropTable from '../../components/common/PropTable.vue'
|
||||
import Dependencies from '../../components/code/Dependencies.vue'
|
||||
import CliInstallation from '../../components/code/CliInstallation.vue'
|
||||
import CodeExample from '../../components/code/CodeExample.vue'
|
||||
import Customize from '../../components/common/Customize.vue'
|
||||
import PreviewSwitch from '../../components/common/PreviewSwitch.vue'
|
||||
import PreviewSlider from '../../components/common/PreviewSlider.vue'
|
||||
import PreviewSelect from '../../components/common/PreviewSelect.vue'
|
||||
import Masonry from '../../content/Components/Masonry/Masonry.vue'
|
||||
import { masonry } from '@/constants/code/Components/masonryCode'
|
||||
import { useForceRerender } from '@/composables/useForceRerender'
|
||||
|
||||
const ease = ref('power3.out')
|
||||
const duration = ref(0.6)
|
||||
const stagger = ref(0.05)
|
||||
const animateFrom = ref<'bottom' | 'top' | 'left' | 'right' | 'center' | 'random'>('bottom')
|
||||
const scaleOnHover = ref(true)
|
||||
const hoverScale = ref(0.95)
|
||||
const blurToFocus = ref(true)
|
||||
const colorShiftOnHover = ref(false)
|
||||
const { rerenderKey, forceRerender } = useForceRerender()
|
||||
|
||||
const sampleItems = ref([
|
||||
{ id: '1', img: 'https://picsum.photos/300/400?random=1&grayscale', url: 'https://picsum.photos', height: 400 },
|
||||
{ id: '2', img: 'https://picsum.photos/300/600?random=2&grayscale', url: 'https://picsum.photos', height: 600 },
|
||||
{ id: '3', img: 'https://picsum.photos/300/350?random=3&grayscale', url: 'https://picsum.photos', height: 350 },
|
||||
{ id: '4', img: 'https://picsum.photos/300/500?random=4&grayscale', url: 'https://picsum.photos', height: 500 },
|
||||
{ id: '5', img: 'https://picsum.photos/300/450?random=5&grayscale', url: 'https://picsum.photos', height: 450 },
|
||||
{ id: '6', img: 'https://picsum.photos/300/380?random=6&grayscale', url: 'https://picsum.photos', height: 380 },
|
||||
{ id: '7', img: 'https://picsum.photos/300/520?random=7&grayscale', url: 'https://picsum.photos', height: 520 },
|
||||
{ id: '8', img: 'https://picsum.photos/300/420?random=8&grayscale', url: 'https://picsum.photos', height: 420 },
|
||||
{ id: '9', img: 'https://picsum.photos/300/480?random=9&grayscale', url: 'https://picsum.photos', height: 480 },
|
||||
{ id: '10', img: 'https://picsum.photos/300/360?random=10&grayscale', url: 'https://picsum.photos', height: 360 },
|
||||
{ id: '11', img: 'https://picsum.photos/300/550?random=11&grayscale', url: 'https://picsum.photos', height: 550 },
|
||||
{ id: '12', img: 'https://picsum.photos/300/400?random=12&grayscale', url: 'https://picsum.photos', height: 400 },
|
||||
{ id: '13', img: 'https://picsum.photos/300/470?random=13&grayscale', url: 'https://picsum.photos', height: 470 },
|
||||
{ id: '14', img: 'https://picsum.photos/300/390?random=14&grayscale', url: 'https://picsum.photos', height: 390 },
|
||||
{ id: '15', img: 'https://picsum.photos/300/510?random=15&grayscale', url: 'https://picsum.photos', height: 510 },
|
||||
])
|
||||
|
||||
const propData = [
|
||||
{ name: 'items', type: 'Item[]', default: '[]', description: 'Array of items to display in the masonry grid. Each item must have id, img, url, and height properties.' },
|
||||
{ name: 'ease', type: 'string', default: '"power3.out"', description: 'GSAP easing function for animations.' },
|
||||
{ name: 'duration', type: 'number', default: '0.6', description: 'Duration of the animation in seconds.' },
|
||||
{ name: 'stagger', type: 'number', default: '0.05', description: 'Stagger delay between item animations in seconds.' },
|
||||
{ name: 'animateFrom', type: 'string', default: '"bottom"', description: 'Direction items animate from: "bottom", "top", "left", "right", "center", or "random".' },
|
||||
{ name: 'scaleOnHover', type: 'boolean', default: 'true', description: 'Whether items scale on hover.' },
|
||||
{ name: 'hoverScale', type: 'number', default: '0.95', description: 'Scale factor when hovering over items.' },
|
||||
{ name: 'blurToFocus', type: 'boolean', default: 'true', description: 'Whether items start blurred and focus on entrance.' },
|
||||
{ name: 'colorShiftOnHover', type: 'boolean', default: 'false', description: 'Whether to show color overlay on hover.' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.masonry-demo-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -13,6 +13,7 @@ import PrimeVue from 'primevue/config'
|
||||
import Aura from '@primeuix/themes/aura'
|
||||
import Button from 'primevue/button'
|
||||
import Toast from 'primevue/toast'
|
||||
import Select from 'primevue/select'
|
||||
import ToastService from 'primevue/toastservice'
|
||||
|
||||
const app = createApp(App)
|
||||
@@ -28,5 +29,6 @@ app.use(ToastService)
|
||||
// Global components
|
||||
app.component('Button', Button)
|
||||
app.component('Toast', Toast)
|
||||
app.component('Select', Select)
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
Reference in New Issue
Block a user