mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 06:29:30 -07:00
Add prettier config, format codebase
This commit is contained in:
4
.github/ISSUE_TEMPLATE/1-bug-report.yml
vendored
4
.github/ISSUE_TEMPLATE/1-bug-report.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name: 🐞 Bug report
|
||||
description: Help improve Vue Bits.
|
||||
labels: ["bug"]
|
||||
title: "[BUG]: "
|
||||
labels: ['bug']
|
||||
title: '[BUG]: '
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/2-feature-request.yml
vendored
4
.github/ISSUE_TEMPLATE/2-feature-request.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name: 💡 Feature Request
|
||||
description: Suggest something for Vue Bits.
|
||||
labels: ["enhancement"]
|
||||
title: "[FEAT]: "
|
||||
labels: ['enhancement']
|
||||
title: '[FEAT]: '
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
||||
34
.prettierignore
Normal file
34
.prettierignore
Normal file
@@ -0,0 +1,34 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Build outputs
|
||||
dist/
|
||||
build/
|
||||
public/ui/
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# IDE files
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Cache
|
||||
.cache/
|
||||
.parcel-cache/
|
||||
.nuxt/
|
||||
.next/
|
||||
.vite/
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Generated files
|
||||
coverage/
|
||||
*.tsbuildinfo
|
||||
14
.prettierrc
Normal file
14
.prettierrc
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 120,
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "avoid",
|
||||
"endOfLine": "lf",
|
||||
"vueIndentScriptAndStyle": false,
|
||||
"htmlWhitespaceSensitivity": "ignore",
|
||||
"bracketSameLine": false,
|
||||
"singleAttributePerLine": false
|
||||
}
|
||||
@@ -62,7 +62,7 @@ Please review the [Contribution Guide](https://github.com/DavidHDev/vue-bits/blo
|
||||
|
||||
## Stats
|
||||
|
||||

|
||||

|
||||
|
||||
## Sponsorship
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { globalIgnores } from 'eslint/config'
|
||||
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
|
||||
import pluginVue from 'eslint-plugin-vue'
|
||||
import { globalIgnores } from 'eslint/config';
|
||||
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript';
|
||||
import pluginVue from 'eslint-plugin-vue';
|
||||
|
||||
export default defineConfigWithVueTs(
|
||||
{
|
||||
name: 'app/files-to-lint',
|
||||
files: ['**/*.{ts,mts,tsx,vue}'],
|
||||
files: ['**/*.{ts,mts,tsx,vue}']
|
||||
},
|
||||
|
||||
globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),
|
||||
@@ -18,7 +18,7 @@ export default defineConfigWithVueTs(
|
||||
files: ['**/*.vue'],
|
||||
rules: {
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'vue/no-reserved-component-names': 'off',
|
||||
},
|
||||
},
|
||||
)
|
||||
'vue/no-reserved-component-names': 'off'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
86
index.html
86
index.html
@@ -1,31 +1,36 @@
|
||||
<!doctype html>
|
||||
<html lang="en" style="background: #0b0b0b;">
|
||||
|
||||
<html lang="en" style="background: #0b0b0b">
|
||||
<head>
|
||||
<!-- Basic Meta Tags -->
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#0b0b0b" />
|
||||
<meta name="author" content="David Haz">
|
||||
<meta name="description"
|
||||
content="An open source collection of high quality, animated, interactive & fully customizable Vue components for building stunning, memorable user interfaces." />
|
||||
<meta name="keywords"
|
||||
content="Vue, Vue tutorials, Vue tips, Vue best practices, Vue development, Vue guides, Vue articles, Vue ecosystem, JavaScript, frontend development, VueJS, UI Library, Component Library, Vue Components, Vue Animations" />
|
||||
<meta name="author" content="David Haz" />
|
||||
<meta
|
||||
name="description"
|
||||
content="An open source collection of high quality, animated, interactive & fully customizable Vue components for building stunning, memorable user interfaces."
|
||||
/>
|
||||
<meta
|
||||
name="keywords"
|
||||
content="Vue, Vue tutorials, Vue tips, Vue best practices, Vue development, Vue guides, Vue articles, Vue ecosystem, JavaScript, frontend development, VueJS, UI Library, Component Library, Vue Components, Vue Animations"
|
||||
/>
|
||||
|
||||
<!-- Font -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap"
|
||||
rel="stylesheet">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,200..800&family=Figtree:ital,wght@0,300..900;1,300..900&family=Figtree:wght@200..800&display=swap"
|
||||
rel="stylesheet">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Gochi+Hand&display=swap" rel="stylesheet">
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Gochi+Hand&display=swap" rel="stylesheet" />
|
||||
|
||||
<!-- Icons -->
|
||||
<link rel="icon" type="image/svg+xml" sizes="16x16 32x32" href="favicon.ico" />
|
||||
@@ -36,34 +41,38 @@
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
|
||||
<!-- Open Graph (OG) - Facebook, LinkedIn, etc. -->
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="Vue Bits">
|
||||
<meta property="og:description"
|
||||
content="An open source collection of high quality, animated, interactive & fully customizable Vue components for building stunning, memorable user interfaces.">
|
||||
<meta property="og:image" content="https://vue-bits.dev/og-pic.png">
|
||||
<meta property="og:image:alt" content="The Vue Bits landing page design, showcasing the logo and a subtitle!">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<meta property="og:url" content="https://vue-bits.dev">
|
||||
<meta property="og:site_name" content="Vue Bits">
|
||||
<meta property="og:locale" content="en_US">
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content="Vue Bits" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="An open source collection of high quality, animated, interactive & fully customizable Vue components for building stunning, memorable user interfaces."
|
||||
/>
|
||||
<meta property="og:image" content="https://vue-bits.dev/og-pic.png" />
|
||||
<meta property="og:image:alt" content="The Vue Bits landing page design, showcasing the logo and a subtitle!" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta property="og:url" content="https://vue-bits.dev" />
|
||||
<meta property="og:site_name" content="Vue Bits" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
|
||||
<!-- Twitter Card - Twitter Sharing -->
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="Vue Bits">
|
||||
<meta name="twitter:description"
|
||||
content="An open source collection of high quality, animated, interactive & fully customizable Vue components for building stunning, memorable user interfaces.">
|
||||
<meta name="twitter:image" content="https://vue-bits.dev/og-pic.jpg">
|
||||
<meta name="twitter:image:alt" content="The Vue Bits landing page design, showcasing the logo and a subtitle!">
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="Vue Bits" />
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="An open source collection of high quality, animated, interactive & fully customizable Vue components for building stunning, memorable user interfaces."
|
||||
/>
|
||||
<meta name="twitter:image" content="https://vue-bits.dev/og-pic.jpg" />
|
||||
<meta name="twitter:image:alt" content="The Vue Bits landing page design, showcasing the logo and a subtitle!" />
|
||||
|
||||
<!-- Favicon & Apple Touch Icons -->
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||
<link rel="manifest" href="/site.webmanifest">
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
|
||||
<!-- Canonical & Robots Meta Tags -->
|
||||
<link rel="canonical" href="https://vue-bits.dev">
|
||||
<meta name="robots" content="index, follow">
|
||||
<link rel="canonical" href="https://vue-bits.dev" />
|
||||
<meta name="robots" content="index, follow" />
|
||||
|
||||
<!-- Structured Data (JSON-LD for SEO) -->
|
||||
<script type="application/ld+json">
|
||||
@@ -95,5 +104,4 @@
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
1
package-lock.json
generated
1
package-lock.json
generated
@@ -41,6 +41,7 @@
|
||||
"jsrepo": "^1.30.1",
|
||||
"npm-run-all2": "^8.0.4",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.6.2",
|
||||
"tailwindcss": "^4.1.11",
|
||||
"typescript": "~5.8.0",
|
||||
"vite": "^7.0.0",
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build",
|
||||
"lint": "eslint . --fix",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"new:component": "node scripts/generateComponent.js"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -47,6 +49,7 @@
|
||||
"jsrepo": "^1.30.1",
|
||||
"npm-run-all2": "^8.0.4",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.6.2",
|
||||
"tailwindcss": "^4.1.11",
|
||||
"typescript": "~5.8.0",
|
||||
"vite": "^7.0.0",
|
||||
|
||||
@@ -1 +1,11 @@
|
||||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
||||
{
|
||||
"name": "",
|
||||
"short_name": "",
|
||||
"icons": [
|
||||
{ "src": "/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" },
|
||||
{ "src": "/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
||||
|
||||
0
scripts/cleanEmptyLines.js
Normal file
0
scripts/cleanEmptyLines.js
Normal file
@@ -1,15 +1,15 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import process from "process";
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import process from 'process';
|
||||
|
||||
import { fileURLToPath } from "url";
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
if (args.length < 2) {
|
||||
console.error("Usage: npm run generate:component <ComponentType> <ComponentName>");
|
||||
console.error('Usage: npm run generate:component <ComponentType> <ComponentName>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -17,12 +17,12 @@ const [componentType, componentName] = args;
|
||||
const componentNameLower = componentName.charAt(0).toLowerCase() + componentName.slice(1);
|
||||
|
||||
const paths = {
|
||||
content: path.join(__dirname, "../src/content", componentType, componentName),
|
||||
demo: path.join(__dirname, "../src/demo", componentType),
|
||||
constants: path.join(__dirname, "../src/constants/code", componentType),
|
||||
content: path.join(__dirname, '../src/content', componentType, componentName),
|
||||
demo: path.join(__dirname, '../src/demo', componentType),
|
||||
constants: path.join(__dirname, '../src/constants/code', componentType)
|
||||
};
|
||||
|
||||
Object.values(paths).forEach((dir) => {
|
||||
Object.values(paths).forEach(dir => {
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
@@ -31,12 +31,12 @@ Object.values(paths).forEach((dir) => {
|
||||
const files = [
|
||||
path.join(paths.content, `${componentName}.vue`),
|
||||
path.join(paths.demo, `${componentName}Demo.vue`),
|
||||
path.join(paths.constants, `${componentNameLower}Code.ts`),
|
||||
path.join(paths.constants, `${componentNameLower}Code.ts`)
|
||||
];
|
||||
|
||||
files.forEach((file) => {
|
||||
files.forEach(file => {
|
||||
if (!fs.existsSync(file)) {
|
||||
fs.writeFileSync(file, "");
|
||||
fs.writeFileSync(file, '');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
23
src/App.vue
23
src/App.vue
@@ -1,27 +1,24 @@
|
||||
<template>
|
||||
<div>
|
||||
<DisplayHeader
|
||||
v-if="!isCategoryPage"
|
||||
:activeItem="activeItem"
|
||||
/>
|
||||
<DisplayHeader v-if="!isCategoryPage" :activeItem="activeItem" />
|
||||
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import DisplayHeader from '@/components/landing/DisplayHeader/DisplayHeader.vue'
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import DisplayHeader from '@/components/landing/DisplayHeader/DisplayHeader.vue';
|
||||
|
||||
const route = useRoute()
|
||||
const route = useRoute();
|
||||
|
||||
const activeItem = computed(() => {
|
||||
if (route.path === '/') return 'home'
|
||||
return null
|
||||
})
|
||||
if (route.path === '/') return 'home';
|
||||
return null;
|
||||
});
|
||||
|
||||
const isCategoryPage = computed(() => {
|
||||
return /^\/[^/]+\/[^/]+$/.test(route.path)
|
||||
})
|
||||
return /^\/[^/]+\/[^/]+$/.test(route.path);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -2,12 +2,21 @@
|
||||
<div class="cli-installation">
|
||||
<h2 class="demo-title">One-Time Installation</h2>
|
||||
|
||||
<VCodeBlock v-if="command" :code="command" :persistent-copy-button="true" highlightjs lang="bash" theme="nord"
|
||||
:copy-button="true" class="code-block" />
|
||||
<VCodeBlock
|
||||
v-if="command"
|
||||
:code="command"
|
||||
:persistent-copy-button="true"
|
||||
highlightjs
|
||||
lang="bash"
|
||||
theme="nord"
|
||||
:copy-button="true"
|
||||
class="code-block"
|
||||
/>
|
||||
|
||||
<div class="cli-divider"></div>
|
||||
|
||||
<h2 class="demo-title">Full CLI Setup</h2>
|
||||
|
||||
<p class="jsrepo-info">
|
||||
Vue Bits uses
|
||||
<a href="https://jsrepo.dev/" target="_blank" rel="noreferrer">jsrepo</a>
|
||||
@@ -17,23 +26,57 @@
|
||||
<Accordion expandIcon="pi pi-chevron-right" collapseIcon="pi pi-chevron-down">
|
||||
<AccordionPanel value="setup">
|
||||
<AccordionHeader>Setup Steps</AccordionHeader>
|
||||
|
||||
<AccordionContent
|
||||
:pt="{ transition: { enterFromClass: '', enterActiveClass: '', enterToClass: '', leaveFromClass: '', leaveActiveClass: '', leaveToClass: '' } }">
|
||||
:pt="{
|
||||
transition: {
|
||||
enterFromClass: '',
|
||||
enterActiveClass: '',
|
||||
enterToClass: '',
|
||||
leaveFromClass: '',
|
||||
leaveActiveClass: '',
|
||||
leaveToClass: ''
|
||||
}
|
||||
}"
|
||||
>
|
||||
<div class="setup-content">
|
||||
<p class="demo-extra-info">1. Initialize a config file for your project</p>
|
||||
|
||||
<div class="setup-option">
|
||||
<VCodeBlock :persistent-copy-button="true" code="npx jsrepo init https://vue-bits.dev/ui" highlightjs
|
||||
lang="bash" theme="nord" :copy-button="true" class="code-block" />
|
||||
<VCodeBlock
|
||||
:persistent-copy-button="true"
|
||||
code="npx jsrepo init https://vue-bits.dev/ui"
|
||||
highlightjs
|
||||
lang="bash"
|
||||
theme="nord"
|
||||
:copy-button="true"
|
||||
class="code-block"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p class="demo-extra-info">2. Browse & add components from the list</p>
|
||||
<VCodeBlock :persistent-copy-button="true" code="npx jsrepo add" highlightjs lang="bash" theme="nord"
|
||||
:copy-button="true" class="code-block" />
|
||||
|
||||
<VCodeBlock
|
||||
:persistent-copy-button="true"
|
||||
code="npx jsrepo add"
|
||||
highlightjs
|
||||
lang="bash"
|
||||
theme="nord"
|
||||
:copy-button="true"
|
||||
class="code-block"
|
||||
/>
|
||||
|
||||
<p class="demo-extra-info">3. Or just add a specific component</p>
|
||||
<VCodeBlock :persistent-copy-button="true" code="npx jsrepo add Animations/AnimatedContainer" highlightjs
|
||||
lang="bash" theme="nord" :copy-button="true" class="code-block" />
|
||||
|
||||
<VCodeBlock
|
||||
:persistent-copy-button="true"
|
||||
code="npx jsrepo add Animations/AnimatedContainer"
|
||||
highlightjs
|
||||
lang="bash"
|
||||
theme="nord"
|
||||
:copy-button="true"
|
||||
class="code-block"
|
||||
/>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionPanel>
|
||||
@@ -42,15 +85,15 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { VCodeBlock } from '@wdns/vue-code-block'
|
||||
import Accordion from 'primevue/accordion'
|
||||
import AccordionPanel from 'primevue/accordionpanel'
|
||||
import AccordionHeader from 'primevue/accordionheader'
|
||||
import AccordionContent from 'primevue/accordioncontent'
|
||||
import { VCodeBlock } from '@wdns/vue-code-block';
|
||||
import Accordion from 'primevue/accordion';
|
||||
import AccordionPanel from 'primevue/accordionpanel';
|
||||
import AccordionHeader from 'primevue/accordionheader';
|
||||
import AccordionContent from 'primevue/accordioncontent';
|
||||
|
||||
const { command } = defineProps<{
|
||||
command?: string
|
||||
}>()
|
||||
command?: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -74,7 +117,7 @@ const { command } = defineProps<{
|
||||
}
|
||||
|
||||
.jsrepo-info a {
|
||||
color: #27FF64;
|
||||
color: #27ff64;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,14 +4,25 @@
|
||||
<h2 class="demo-title">{{ getDisplayName(name) }}</h2>
|
||||
|
||||
<div v-if="snippet" class="code-container">
|
||||
<div class="code-wrapper" :class="{ 'collapsed': shouldCollapse(snippet) && !isExpanded(name) }">
|
||||
<VCodeBlock :code="snippet" highlightjs :lang="getLanguage(name)" theme="nord" :copy-button="true"
|
||||
:persistent-copy-button="true" class="code-block" />
|
||||
<div class="code-wrapper" :class="{ collapsed: shouldCollapse(snippet) && !isExpanded(name) }">
|
||||
<VCodeBlock
|
||||
:code="snippet"
|
||||
highlightjs
|
||||
:lang="getLanguage(name)"
|
||||
theme="nord"
|
||||
:copy-button="true"
|
||||
:persistent-copy-button="true"
|
||||
class="code-block"
|
||||
/>
|
||||
|
||||
<div v-if="shouldCollapse(snippet) && !isExpanded(name)" class="fade-overlay" />
|
||||
|
||||
<button v-if="shouldCollapse(snippet)" class="expand-button" :class="{ 'expanded': isExpanded(name) }"
|
||||
@click="toggleExpanded(name)">
|
||||
<button
|
||||
v-if="shouldCollapse(snippet)"
|
||||
class="expand-button"
|
||||
:class="{ expanded: isExpanded(name) }"
|
||||
@click="toggleExpanded(name)"
|
||||
>
|
||||
{{ isExpanded(name) ? 'Collapse Snippet' : 'See Full Snippet' }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -19,6 +30,7 @@
|
||||
|
||||
<div v-if="!snippet" class="no-code">
|
||||
<span>Nothing here yet!</span>
|
||||
|
||||
<i class="pi pi-face-sad"></i>
|
||||
</div>
|
||||
</div>
|
||||
@@ -26,58 +38,55 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { VCodeBlock } from '@wdns/vue-code-block'
|
||||
import type { CodeObject } from '../../types/code'
|
||||
import { computed, ref } from 'vue';
|
||||
import { VCodeBlock } from '@wdns/vue-code-block';
|
||||
import type { CodeObject } from '../../types/code';
|
||||
|
||||
const props = defineProps<{
|
||||
codeObject: CodeObject
|
||||
}>()
|
||||
codeObject: CodeObject;
|
||||
}>();
|
||||
|
||||
const skipKeys = [
|
||||
'cli'
|
||||
]
|
||||
const skipKeys = ['cli'];
|
||||
|
||||
const expandedSections = ref<Set<string>>(new Set())
|
||||
const expandedSections = ref<Set<string>>(new Set());
|
||||
|
||||
const codeEntries = computed(() => {
|
||||
return Object.entries(props.codeObject).filter(([name]) => !skipKeys.includes(name))
|
||||
})
|
||||
|
||||
return Object.entries(props.codeObject).filter(([name]) => !skipKeys.includes(name));
|
||||
});
|
||||
|
||||
const shouldCollapse = (snippet: string) => {
|
||||
const codeLines = snippet?.split('\n').length || 0
|
||||
return codeLines > 35
|
||||
}
|
||||
const codeLines = snippet?.split('\n').length || 0;
|
||||
return codeLines > 35;
|
||||
};
|
||||
|
||||
const isExpanded = (name: string) => {
|
||||
return expandedSections.value.has(name)
|
||||
}
|
||||
return expandedSections.value.has(name);
|
||||
};
|
||||
|
||||
const toggleExpanded = (name: string) => {
|
||||
if (expandedSections.value.has(name)) {
|
||||
expandedSections.value.delete(name)
|
||||
expandedSections.value.delete(name);
|
||||
} else {
|
||||
expandedSections.value.add(name)
|
||||
}
|
||||
expandedSections.value.add(name);
|
||||
}
|
||||
};
|
||||
|
||||
const getDisplayName = (name: string) => {
|
||||
if (name === 'code') return 'Code'
|
||||
if (name === 'cli') return 'CLI Command'
|
||||
if (name === 'utility') return 'Utility'
|
||||
if (name === 'usage') return 'Usage'
|
||||
if (name === 'installation') return 'Installation'
|
||||
return name.charAt(0).toUpperCase() + name.slice(1)
|
||||
}
|
||||
if (name === 'code') return 'Code';
|
||||
if (name === 'cli') return 'CLI Command';
|
||||
if (name === 'utility') return 'Utility';
|
||||
if (name === 'usage') return 'Usage';
|
||||
if (name === 'installation') return 'Installation';
|
||||
return name.charAt(0).toUpperCase() + name.slice(1);
|
||||
};
|
||||
|
||||
const getLanguage = (name: string) => {
|
||||
if (name === 'cli') return 'bash'
|
||||
if (name === 'code') return 'html'
|
||||
if (name === 'usage') return 'html'
|
||||
if (name === 'installation') return 'bash'
|
||||
return 'javascript'
|
||||
}
|
||||
if (name === 'cli') return 'bash';
|
||||
if (name === 'code') return 'html';
|
||||
if (name === 'usage') return 'html';
|
||||
if (name === 'installation') return 'bash';
|
||||
return 'javascript';
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
<template>
|
||||
<div class="dependencies-container">
|
||||
<h2 class="demo-title">Dependencies</h2>
|
||||
|
||||
<div class="demo-details">
|
||||
<span v-for="dep in dependencyList" :key="dep" class="dependency-tag">
|
||||
{{ dep }}
|
||||
</span>
|
||||
<span v-for="dep in dependencyList" :key="dep" class="dependency-tag">{{ dep }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
dependencyList: string[]
|
||||
dependencyList: string[];
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
defineProps<Props>();
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<Button :style="{
|
||||
<Button
|
||||
:style="{
|
||||
fontWeight: 500,
|
||||
borderRadius: '0.75rem',
|
||||
border: '1px solid #142216',
|
||||
@@ -12,18 +13,21 @@
|
||||
opacity: visible ? 1 : 0,
|
||||
bottom: visible ? '2.5em' : '1em',
|
||||
cursor: visible ? 'pointer' : 'default'
|
||||
}" class="back-to-top" @click="visible && scrollToTop()">
|
||||
<i class="pi pi-arrow-up" style="color: #fff; font-size: 1rem;"></i>
|
||||
}"
|
||||
class="back-to-top"
|
||||
@click="visible && scrollToTop()"
|
||||
>
|
||||
<i class="pi pi-arrow-up" style="color: #fff; font-size: 1rem"></i>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { useToast } from 'primevue/usetoast'
|
||||
import Button from 'primevue/button'
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import Button from 'primevue/button';
|
||||
|
||||
const toast = useToast()
|
||||
const visible = ref(false)
|
||||
const toast = useToast();
|
||||
const visible = ref(false);
|
||||
|
||||
const messages = [
|
||||
'🐴 Country roads, take me home!',
|
||||
@@ -33,34 +37,33 @@ const messages = [
|
||||
'🐉 Fus Ro Dah!',
|
||||
'🍄 The princess is in another castle!',
|
||||
'🦸♂️ Avengers, assemble!',
|
||||
'🗡️ It\'s dangerous to go alone! Take this.',
|
||||
"🗡️ It's dangerous to go alone! Take this.",
|
||||
'📜 A wizard is never late.',
|
||||
'💍 Foul Tarnished, in search of the Elden Ring!',
|
||||
'🐊 See you later, alligator.',
|
||||
'🔥 Dracarys!'
|
||||
]
|
||||
];
|
||||
|
||||
const getRandomMessage = (messages: string[]) =>
|
||||
messages[Math.floor(Math.random() * messages.length)]
|
||||
const getRandomMessage = (messages: string[]) => messages[Math.floor(Math.random() * messages.length)];
|
||||
|
||||
const scrollToTop = () => {
|
||||
window.scrollTo(0, 0)
|
||||
window.scrollTo(0, 0);
|
||||
toast.add({
|
||||
severity: 'secondary',
|
||||
summary: getRandomMessage(messages),
|
||||
life: 3000
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onScroll = () => {
|
||||
visible.value = window.scrollY > 500
|
||||
}
|
||||
visible.value = window.scrollY > 500;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('scroll', onScroll)
|
||||
})
|
||||
window.addEventListener('scroll', onScroll);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('scroll', onScroll)
|
||||
})
|
||||
window.removeEventListener('scroll', onScroll);
|
||||
});
|
||||
</script>
|
||||
@@ -1,24 +1,19 @@
|
||||
<template>
|
||||
<div class="contribute-container">
|
||||
<h2 class="demo-title-contribute">Help improve this component!</h2>
|
||||
|
||||
<div class="contribute-buttons">
|
||||
<a
|
||||
:href="bugReportUrl"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
class="contribute-button"
|
||||
>
|
||||
<a :href="bugReportUrl" target="_blank" rel="noreferrer" class="contribute-button">
|
||||
<i class="pi pi-exclamation-triangle"></i>
|
||||
|
||||
<span>Report an issue</span>
|
||||
</a>
|
||||
|
||||
<span class="contribute-separator">or</span>
|
||||
<a
|
||||
:href="featureRequestUrl"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
class="contribute-button"
|
||||
>
|
||||
|
||||
<a :href="featureRequestUrl" target="_blank" rel="noreferrer" class="contribute-button">
|
||||
<i class="pi pi-lightbulb"></i>
|
||||
|
||||
<span>Request a feature</span>
|
||||
</a>
|
||||
</div>
|
||||
@@ -26,22 +21,22 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
const route = useRoute()
|
||||
const route = useRoute();
|
||||
|
||||
const bugReportUrl = computed(() => {
|
||||
const category = route.params.category
|
||||
const subcategory = route.params.subcategory
|
||||
const title = encodeURIComponent(`[BUG]: ${category}/${subcategory}`)
|
||||
return `https://github.com/DavidHDev/vue-bits/issues/new?template=1-bug-report.yml&title=${title}&labels=bug`
|
||||
})
|
||||
const category = route.params.category;
|
||||
const subcategory = route.params.subcategory;
|
||||
const title = encodeURIComponent(`[BUG]: ${category}/${subcategory}`);
|
||||
return `https://github.com/DavidHDev/vue-bits/issues/new?template=1-bug-report.yml&title=${title}&labels=bug`;
|
||||
});
|
||||
|
||||
const featureRequestUrl = computed(() => {
|
||||
const category = route.params.category
|
||||
const subcategory = route.params.subcategory
|
||||
const title = encodeURIComponent(`[FEAT]: ${category}/${subcategory}`)
|
||||
return `https://github.com/DavidHDev/vue-bits/issues/new?template=2-feature-request.yml&title=${title}&labels=enhancement`
|
||||
})
|
||||
const category = route.params.category;
|
||||
const subcategory = route.params.subcategory;
|
||||
const title = encodeURIComponent(`[FEAT]: ${category}/${subcategory}`);
|
||||
return `https://github.com/DavidHDev/vue-bits/issues/new?template=2-feature-request.yml&title=${title}&labels=enhancement`;
|
||||
});
|
||||
</script>
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="demo-title">Customize</h2>
|
||||
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,28 +1,45 @@
|
||||
<template>
|
||||
<svg width="141" height="30" viewBox="0 0 193 41" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M66.4663 34.2676L56.3843 7.12372H60.5722L68.7929 30.2348L77.0912 7.12372H81.2015L71.1195 34.2676H66.4663Z"
|
||||
fill="white" />
|
||||
<path
|
||||
d="M66.4663 34.2676L56.3843 7.12372H60.5722L68.7929 30.2348L77.0912 7.12372H81.2015L71.1195 34.2676H66.4663Z"
|
||||
fill="white"
|
||||
/>
|
||||
|
||||
<path
|
||||
d="M88.3915 34.7329C86.8662 34.7329 85.5349 34.4227 84.3974 33.8023C83.2858 33.1818 82.4198 32.2512 81.7994 31.0103C81.2048 29.7695 80.9075 28.2055 80.9075 26.3183V14.724H84.7852V25.8918C84.7852 27.7272 85.1859 29.1103 85.9873 30.0409C86.7887 30.9716 87.9391 31.4369 89.4384 31.4369C90.4466 31.4369 91.3514 31.1913 92.1528 30.7001C92.9801 30.2089 93.6264 29.498 94.0917 28.5674C94.557 27.6367 94.7897 26.4993 94.7897 25.155V14.724H98.6674V34.2676H95.2162L94.9448 30.9328C94.3502 32.1219 93.4842 33.0526 92.3467 33.7247C91.2092 34.3969 89.8908 34.7329 88.3915 34.7329Z"
|
||||
fill="white" />
|
||||
fill="white"
|
||||
/>
|
||||
|
||||
<path
|
||||
d="M110.648 34.7329C108.787 34.7329 107.133 34.3064 105.685 33.4533C104.237 32.6002 103.1 31.411 102.273 29.8858C101.471 28.3606 101.071 26.5898 101.071 24.5734C101.071 22.5053 101.471 20.7086 102.273 19.1834C103.1 17.6323 104.237 16.4302 105.685 15.5771C107.133 14.6982 108.813 14.2587 110.726 14.2587C112.639 14.2587 114.281 14.6852 115.651 15.5383C117.021 16.3914 118.081 17.5289 118.83 18.9507C119.58 20.3467 119.955 21.8977 119.955 23.6039C119.955 23.8624 119.942 24.1468 119.916 24.457C119.916 24.7414 119.903 25.0645 119.877 25.4265H103.901V22.6733H116.077C116 21.0447 115.457 19.7779 114.449 18.8731C113.44 17.9425 112.187 17.4772 110.687 17.4772C109.627 17.4772 108.658 17.7228 107.779 18.2139C106.9 18.6793 106.189 19.3772 105.646 20.3079C105.129 21.2127 104.871 22.3631 104.871 23.759V24.8448C104.871 26.2925 105.129 27.5204 105.646 28.5286C106.189 29.511 106.9 30.2606 107.779 30.7777C108.658 31.2689 109.614 31.5144 110.648 31.5144C111.889 31.5144 112.91 31.243 113.712 30.7001C114.513 30.1572 115.108 29.4205 115.496 28.4898H119.373C119.037 29.679 118.468 30.7518 117.667 31.7083C116.866 32.639 115.87 33.3757 114.681 33.9186C113.518 34.4615 112.174 34.7329 110.648 34.7329Z"
|
||||
fill="white" />
|
||||
fill="white"
|
||||
/>
|
||||
|
||||
<path
|
||||
d="M130.615 34.2676V7.12372H140.658C142.545 7.12372 144.122 7.43394 145.389 8.05437C146.656 8.64895 147.599 9.47619 148.22 10.5361C148.866 11.5701 149.189 12.7464 149.189 14.0648C149.189 15.4349 148.892 16.5853 148.297 17.5159C147.703 18.4466 146.914 19.1704 145.932 19.6875C144.975 20.1786 143.941 20.463 142.83 20.5406L143.373 20.1528C144.562 20.1786 145.648 20.5018 146.63 21.1222C147.612 21.7168 148.388 22.5182 148.956 23.5264C149.525 24.5346 149.81 25.6462 149.81 26.8612C149.81 28.2572 149.474 29.5239 148.801 30.6613C148.129 31.773 147.134 32.6519 145.816 33.2982C144.497 33.9445 142.881 34.2676 140.968 34.2676H130.615ZM134.493 31.0491H140.464C142.171 31.0491 143.489 30.6613 144.42 29.8858C145.376 29.0844 145.854 27.9599 145.854 26.5122C145.854 25.0904 145.363 23.9529 144.381 23.0998C143.424 22.2467 142.093 21.8202 140.387 21.8202H134.493V31.0491ZM134.493 18.8344H140.232C141.86 18.8344 143.101 18.4595 143.954 17.7098C144.807 16.9343 145.234 15.8744 145.234 14.5301C145.234 13.2376 144.807 12.2164 143.954 11.4667C143.101 10.6912 141.822 10.3034 140.115 10.3034H134.493V18.8344Z"
|
||||
fill="white" />
|
||||
fill="white"
|
||||
/>
|
||||
|
||||
<path
|
||||
d="M153.736 34.2676V14.724H157.614V34.2676H153.736ZM155.714 11.0402C154.964 11.0402 154.344 10.8075 153.852 10.3422C153.387 9.87689 153.154 9.2823 153.154 8.55847C153.154 7.86048 153.387 7.29175 153.852 6.85228C154.344 6.38696 154.964 6.1543 155.714 6.1543C156.438 6.1543 157.045 6.38696 157.536 6.85228C158.027 7.29175 158.273 7.86048 158.273 8.55847C158.273 9.2823 158.027 9.87689 157.536 10.3422C157.045 10.8075 156.438 11.0402 155.714 11.0402Z"
|
||||
fill="white" />
|
||||
fill="white"
|
||||
/>
|
||||
|
||||
<path
|
||||
d="M170.449 34.2676C169.208 34.2676 168.135 34.0737 167.23 33.6859C166.326 33.2982 165.628 32.6519 165.136 31.7471C164.645 30.8423 164.4 29.6144 164.4 28.0633V18.02H161.026V14.724H164.4L164.865 9.83811H168.277V14.724H173.823V18.02H168.277V28.1021C168.277 29.2137 168.51 29.9763 168.975 30.3899C169.441 30.7777 170.242 30.9716 171.38 30.9716H173.629V34.2676H170.449Z"
|
||||
fill="white" />
|
||||
fill="white"
|
||||
/>
|
||||
|
||||
<path
|
||||
d="M184.885 34.7329C183.23 34.7329 181.782 34.4615 180.542 33.9186C179.301 33.3757 178.318 32.6131 177.595 31.6308C176.871 30.6484 176.431 29.498 176.276 28.1796H180.231C180.361 28.8 180.606 29.3688 180.968 29.8858C181.356 30.4028 181.873 30.8165 182.519 31.1267C183.191 31.4369 183.98 31.592 184.885 31.592C185.738 31.592 186.436 31.4757 186.979 31.243C187.547 30.9845 187.961 30.6484 188.219 30.2348C188.478 29.7953 188.607 29.33 188.607 28.8388C188.607 28.115 188.426 27.5721 188.064 27.2102C187.728 26.8224 187.211 26.5251 186.513 26.3183C185.841 26.0857 185.027 25.8789 184.07 25.6979C183.165 25.5428 182.287 25.336 181.433 25.0775C180.606 24.7931 179.856 24.4441 179.184 24.0305C178.538 23.6169 178.021 23.0998 177.633 22.4794C177.246 21.8331 177.052 21.0447 177.052 20.114C177.052 19.0024 177.349 18.0071 177.943 17.1282C178.538 16.2234 179.378 15.5254 180.464 15.0342C181.576 14.5172 182.881 14.2587 184.38 14.2587C186.552 14.2587 188.297 14.7757 189.615 15.8098C190.934 16.8438 191.709 18.3044 191.942 20.1916H188.181C188.077 19.3126 187.689 18.6405 187.017 18.1752C186.345 17.684 185.453 17.4384 184.342 17.4384C183.23 17.4384 182.377 17.6581 181.782 18.0976C181.188 18.5371 180.891 19.1187 180.891 19.8426C180.891 20.3079 181.059 20.7215 181.395 21.0834C181.731 21.4453 182.222 21.7556 182.868 22.0141C183.54 22.2467 184.355 22.4665 185.311 22.6733C186.681 22.9318 187.909 23.2549 188.995 23.6427C190.081 24.0305 190.947 24.5992 191.593 25.3489C192.239 26.0986 192.562 27.1714 192.562 28.5674C192.588 29.7824 192.278 30.8552 191.632 31.7859C191.011 32.7165 190.119 33.4404 188.956 33.9574C187.819 34.4744 186.462 34.7329 184.885 34.7329Z"
|
||||
fill="white" />
|
||||
fill="white"
|
||||
/>
|
||||
|
||||
<path
|
||||
d="M0.345215 0.450195H15.6363C15.6363 0.450195 22.0583 12.3486 26.6885 19.6614C26.8836 19.9694 26.9797 20.151 27.1894 20.4493C27.3852 20.7278 27.4954 20.8853 27.7214 21.1399C27.9339 21.3795 27.8963 21.356 28.1397 21.6249C28.2726 21.7719 28.3875 21.8829 28.6738 22.068C28.933 22.2355 29.377 22.3481 29.7138 22.2345C29.9746 22.1465 30.0451 22.1136 30.2668 21.9894C31.043 21.5543 31.8402 19.9698 31.8402 19.9698L40.7527 3.71523H32.9372V0.450859L46.3206 0.450195L35.594 19.0675C35.594 19.0675 34.5027 20.8176 33.7209 22.1763C33.5935 22.3977 33.0493 23.1986 32.8374 23.4686C32.4369 23.9791 32.2132 24.2841 31.8402 24.6523C31.4828 25.005 31.1302 25.1777 30.8799 25.2783C30.4928 25.4337 30.0788 25.5121 29.663 25.546C29.3087 25.5749 29.0702 25.5934 28.7179 25.546C28.2786 25.4868 28.2472 25.4717 27.7214 25.2783C27.5003 25.197 27.131 24.9776 26.783 24.7266C26.3964 24.4478 26.1328 24.1992 25.6981 23.7395C25.3155 23.3347 25.1467 23.0947 24.8373 22.6316C24.5125 22.1455 24.2598 21.8766 23.953 21.379C19.7839 14.6168 13.8639 3.71523 13.8639 3.71523H5.99112L23.3635 33.9098L26.6885 28.1777C26.6885 28.1777 27.1444 28.7994 28.7438 28.7475C30.2137 28.6998 30.5672 28.1777 30.5672 28.1777L23.3635 40.437L3.53103 6.05657L2.19899 3.71523L0.345215 0.450195Z"
|
||||
fill="white" />
|
||||
fill="white"
|
||||
/>
|
||||
|
||||
<circle cx="29.3282" cy="10.6089" r="3.34281" fill="white" />
|
||||
</svg>
|
||||
</template>
|
||||
@@ -30,5 +47,5 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'VueBitsLogo'
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
<template>
|
||||
<div class="preview-color">
|
||||
<span class="color-label">{{ title }}</span>
|
||||
|
||||
<input :value="modelValue" @input="handleColorChange" type="color" :disabled="disabled" class="color-input" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
title: string
|
||||
modelValue: string
|
||||
disabled?: boolean
|
||||
}>()
|
||||
title: string;
|
||||
modelValue: string;
|
||||
disabled?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: string]
|
||||
}>()
|
||||
'update:modelValue': [value: string];
|
||||
}>();
|
||||
|
||||
const handleColorChange = (event: Event) => {
|
||||
const target = event.target as HTMLInputElement
|
||||
emit('update:modelValue', target.value)
|
||||
}
|
||||
const target = event.target as HTMLInputElement;
|
||||
emit('update:modelValue', target.value);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div class="preview-select">
|
||||
<span class="select-label">{{ title }}</span>
|
||||
|
||||
<Select
|
||||
:model-value="modelValue"
|
||||
@update:model-value="handleSelectChange"
|
||||
@@ -14,45 +15,45 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import Select from 'primevue/select'
|
||||
import { computed } from 'vue';
|
||||
import Select from 'primevue/select';
|
||||
|
||||
interface Option {
|
||||
label: string
|
||||
value: string | number
|
||||
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
|
||||
}>()
|
||||
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]
|
||||
}>()
|
||||
'update:modelValue': [value: string | number];
|
||||
}>();
|
||||
|
||||
const handleSelectChange = (value: string | number) => {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
emit('update:modelValue', value);
|
||||
};
|
||||
|
||||
const isObjectArray = computed(() => {
|
||||
return props.options.length > 0 && typeof props.options[0] === 'object'
|
||||
})
|
||||
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 {}
|
||||
})
|
||||
return {};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div class="preview-slider">
|
||||
<span class="slider-label">{{ title }}</span>
|
||||
|
||||
<Slider
|
||||
:model-value="modelValue"
|
||||
@update:model-value="handleSliderChange"
|
||||
@@ -10,31 +11,32 @@
|
||||
:disabled="disabled"
|
||||
class="custom-slider"
|
||||
/>
|
||||
|
||||
<span class="slider-value">{{ modelValue }}{{ valueUnit }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Slider from 'primevue/slider'
|
||||
import Slider from 'primevue/slider';
|
||||
|
||||
defineProps<{
|
||||
title: string
|
||||
modelValue: number
|
||||
min?: number
|
||||
max?: number
|
||||
step?: number
|
||||
valueUnit?: string
|
||||
disabled?: boolean
|
||||
}>()
|
||||
title: string;
|
||||
modelValue: number;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
valueUnit?: string;
|
||||
disabled?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: number]
|
||||
}>()
|
||||
'update:modelValue': [value: number];
|
||||
}>();
|
||||
|
||||
const handleSliderChange = (value: number | number[]) => {
|
||||
const numValue = Array.isArray(value) ? value[0] : value
|
||||
emit('update:modelValue', numValue)
|
||||
}
|
||||
const numValue = Array.isArray(value) ? value[0] : value;
|
||||
emit('update:modelValue', numValue);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div class="preview-switch">
|
||||
<span class="switch-label">{{ title }}</span>
|
||||
|
||||
<ToggleSwitch
|
||||
:model-value="modelValue"
|
||||
@update:model-value="$emit('update:modelValue', $event)"
|
||||
@@ -10,17 +11,17 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ToggleSwitch from 'primevue/toggleswitch'
|
||||
import ToggleSwitch from 'primevue/toggleswitch';
|
||||
|
||||
defineProps<{
|
||||
title: string
|
||||
modelValue: boolean
|
||||
disabled?: boolean
|
||||
}>()
|
||||
title: string;
|
||||
modelValue: boolean;
|
||||
disabled?: boolean;
|
||||
}>();
|
||||
|
||||
defineEmits<{
|
||||
'update:modelValue': [value: boolean]
|
||||
}>()
|
||||
'update:modelValue': [value: boolean];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div class="prop-table-container">
|
||||
<h2 class="demo-title">Props</h2>
|
||||
|
||||
<div class="table-wrapper">
|
||||
<DataTable :value="data" class="props-table">
|
||||
<Column field="name" header="Property">
|
||||
@@ -8,16 +9,19 @@
|
||||
<div class="code-cell">{{ data.name }}</div>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<Column field="type" header="Type">
|
||||
<template #body="{ data }">
|
||||
<span class="type-text">{{ data.type }}</span>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<Column field="default" header="Default">
|
||||
<template #body="{ data }">
|
||||
<div class="code-cell">{{ data.default || '—' }}</div>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<Column field="description" header="Description">
|
||||
<template #body="{ data }">
|
||||
<div class="description-text">{{ data.description }}</div>
|
||||
@@ -29,19 +33,19 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import DataTable from 'primevue/datatable'
|
||||
import Column from 'primevue/column'
|
||||
import DataTable from 'primevue/datatable';
|
||||
import Column from 'primevue/column';
|
||||
|
||||
interface PropData {
|
||||
name: string
|
||||
type: string
|
||||
default: string
|
||||
description: string
|
||||
name: string;
|
||||
type: string;
|
||||
default: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
data: PropData[]
|
||||
}>()
|
||||
data: PropData[];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Button from 'primevue/button'
|
||||
import Button from 'primevue/button';
|
||||
|
||||
defineEmits<{
|
||||
refresh: []
|
||||
}>()
|
||||
refresh: [];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -5,24 +5,31 @@
|
||||
<Tab value="0">
|
||||
<div class="tab-header">
|
||||
<i class="pi pi-eye"></i>
|
||||
|
||||
<span>Preview</span>
|
||||
</div>
|
||||
</Tab>
|
||||
|
||||
<Tab value="1">
|
||||
<div class="tab-header">
|
||||
<i class="pi pi-code"></i>
|
||||
|
||||
<span>Code</span>
|
||||
</div>
|
||||
</Tab>
|
||||
|
||||
<Tab value="2">
|
||||
<div class="tab-header">
|
||||
<i class="pi pi-box"></i>
|
||||
|
||||
<span>CLI</span>
|
||||
</div>
|
||||
</Tab>
|
||||
|
||||
<Tab value="3">
|
||||
<div class="tab-header">
|
||||
<i class="pi pi-heart"></i>
|
||||
|
||||
<span>Contribute</span>
|
||||
</div>
|
||||
</Tab>
|
||||
@@ -32,12 +39,15 @@
|
||||
<TabPanel value="0">
|
||||
<slot name="preview" />
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel value="1">
|
||||
<slot name="code" />
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel value="2">
|
||||
<slot name="cli" />
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel value="3">
|
||||
<ContributionSection />
|
||||
</TabPanel>
|
||||
@@ -47,12 +57,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Tabs from 'primevue/tabs'
|
||||
import TabList from 'primevue/tablist'
|
||||
import Tab from 'primevue/tab'
|
||||
import TabPanels from 'primevue/tabpanels'
|
||||
import TabPanel from 'primevue/tabpanel'
|
||||
import ContributionSection from './ContributionSection.vue'
|
||||
import Tabs from 'primevue/tabs';
|
||||
import TabList from 'primevue/tablist';
|
||||
import Tab from 'primevue/tab';
|
||||
import TabPanels from 'primevue/tabpanels';
|
||||
import TabPanel from 'primevue/tabpanel';
|
||||
import ContributionSection from './ContributionSection.vue';
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -121,8 +131,8 @@ import ContributionSection from './ContributionSection.vue'
|
||||
:deep(.p-tab-indicator),
|
||||
:deep(.p-tab::before),
|
||||
:deep(.p-tab::after),
|
||||
:deep(.p-tab[aria-selected="true"]::before),
|
||||
:deep(.p-tab[aria-selected="true"]::after),
|
||||
:deep(.p-tab[aria-selected='true']::before),
|
||||
:deep(.p-tab[aria-selected='true']::after),
|
||||
:deep(.p-tablist::after),
|
||||
:deep(.p-tablist-tab-list::before),
|
||||
:deep(.p-tablist-tab-list::after),
|
||||
@@ -131,14 +141,14 @@ import ContributionSection from './ContributionSection.vue'
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
:deep(.p-tab[aria-selected="true"]) {
|
||||
:deep(.p-tab[aria-selected='true']) {
|
||||
background: transparent !important;
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
:deep(.p-tab[aria-selected="true"] .tab-header) {
|
||||
:deep(.p-tab[aria-selected='true'] .tab-header) {
|
||||
background: #333333;
|
||||
color: #A7EF9E;
|
||||
color: #a7ef9e;
|
||||
}
|
||||
|
||||
:deep(.p-tabpanels) {
|
||||
|
||||
@@ -77,7 +77,9 @@
|
||||
color: inherit;
|
||||
font-weight: 400;
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.3s ease, transform 0.2s ease;
|
||||
transition:
|
||||
opacity 0.3s ease,
|
||||
transform 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
@@ -106,9 +108,7 @@
|
||||
font-weight: 500;
|
||||
padding: 0 0 0 1.4rem;
|
||||
height: calc(60px - 2px);
|
||||
background: linear-gradient(135deg,
|
||||
rgb(30, 160, 63),
|
||||
rgba(24, 47, 255, 0.6));
|
||||
background: linear-gradient(135deg, rgb(30, 160, 63), rgba(24, 47, 255, 0.6));
|
||||
background-size: 200% 200%;
|
||||
backdrop-filter: blur(25px);
|
||||
-webkit-backdrop-filter: blur(25px);
|
||||
@@ -121,14 +121,14 @@
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
justify-content: space-between;
|
||||
transition: .3s ease;
|
||||
transition: 0.3s ease;
|
||||
}
|
||||
|
||||
.cta-button span {
|
||||
background-color: #0b0b0b;
|
||||
margin-left: 1em;
|
||||
margin-right: calc(1em - 8px);
|
||||
padding-top: .1em;
|
||||
padding-top: 0.1em;
|
||||
height: 45px;
|
||||
border-radius: 50px;
|
||||
width: 100px;
|
||||
@@ -143,16 +143,16 @@
|
||||
margin-right: 6px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
transition: .3s ease;
|
||||
transition: 0.3s ease;
|
||||
}
|
||||
|
||||
.cta-button:hover {
|
||||
transition: .3s ease;
|
||||
transition: 0.3s ease;
|
||||
}
|
||||
|
||||
.cta-button:hover span img {
|
||||
transform: scale(1.2);
|
||||
transition: .3s ease;
|
||||
transition: 0.3s ease;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
|
||||
@@ -7,22 +7,12 @@
|
||||
|
||||
<div class="nav-cta-group">
|
||||
<nav class="landing-nav-items" ref="navRef">
|
||||
<router-link
|
||||
class="nav-link"
|
||||
:class="{ 'active-link': activeItem === 'home' }"
|
||||
to="/"
|
||||
>
|
||||
Home
|
||||
</router-link>
|
||||
<router-link class="nav-link" to="/text-animations/split-text">
|
||||
Docs
|
||||
</router-link>
|
||||
<router-link class="nav-link" :class="{ 'active-link': activeItem === 'home' }" to="/">Home</router-link>
|
||||
|
||||
<router-link class="nav-link" to="/text-animations/split-text">Docs</router-link>
|
||||
</nav>
|
||||
|
||||
<button
|
||||
class="cta-button"
|
||||
@click="openGitHub"
|
||||
>
|
||||
<button class="cta-button" @click="openGitHub">
|
||||
Star On GitHub
|
||||
<span ref="starCountRef" :style="{ opacity: 0 }">
|
||||
<img :src="starIcon" alt="Star Icon" />
|
||||
@@ -35,30 +25,33 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { gsap } from 'gsap'
|
||||
import VueBitsLogo from '@/components/common/Logo.vue'
|
||||
import { useStars } from '@/composables/useStars'
|
||||
import starIcon from '@/assets/common/star.svg'
|
||||
import './DisplayHeader.css'
|
||||
import { ref, watch } from 'vue';
|
||||
import { gsap } from 'gsap';
|
||||
import VueBitsLogo from '@/components/common/Logo.vue';
|
||||
import { useStars } from '@/composables/useStars';
|
||||
import starIcon from '@/assets/common/star.svg';
|
||||
import './DisplayHeader.css';
|
||||
|
||||
interface Props {
|
||||
activeItem?: string | null;
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
defineProps<Props>();
|
||||
|
||||
const navRef = ref<HTMLElement | null>(null)
|
||||
const starCountRef = ref<HTMLElement | null>(null)
|
||||
const stars = useStars()
|
||||
const navRef = ref<HTMLElement | null>(null);
|
||||
const starCountRef = ref<HTMLElement | null>(null);
|
||||
const stars = useStars();
|
||||
|
||||
const openGitHub = () => {
|
||||
window.open('https://github.com/DavidHDev/vue-bits', '_blank')
|
||||
}
|
||||
window.open('https://github.com/DavidHDev/vue-bits', '_blank');
|
||||
};
|
||||
|
||||
watch(stars, (newStars) => {
|
||||
watch(
|
||||
stars,
|
||||
newStars => {
|
||||
if (newStars && starCountRef.value) {
|
||||
gsap.fromTo(starCountRef.value,
|
||||
gsap.fromTo(
|
||||
starCountRef.value,
|
||||
{
|
||||
scale: 0,
|
||||
width: 0,
|
||||
@@ -66,12 +59,14 @@ watch(stars, (newStars) => {
|
||||
},
|
||||
{
|
||||
scale: 1,
|
||||
width: "100px",
|
||||
width: '100px',
|
||||
opacity: 1,
|
||||
duration: 0.8,
|
||||
ease: "back.out(1)"
|
||||
ease: 'back.out(1)'
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}, { immediate: true })
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
font-weight: 600;
|
||||
letter-spacing: -2px;
|
||||
color: #fff;
|
||||
margin-bottom: .2rem;
|
||||
margin-bottom: 0.2rem;
|
||||
background: linear-gradient(135deg, #fff 0%, #60fa89 20%, #55f788 40%, #00ff62 60%, #55f799 80%, #fff 100%);
|
||||
background-size: 200% 200%;
|
||||
-webkit-background-clip: text;
|
||||
@@ -44,7 +44,6 @@
|
||||
}
|
||||
|
||||
@keyframes gradientShift {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
@@ -120,8 +119,6 @@
|
||||
grid-column: 2 / 3;
|
||||
grid-row: 2 / 3;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@media (min-width: 50rem) {
|
||||
@@ -145,8 +142,6 @@
|
||||
grid-column: 1 / 3;
|
||||
grid-row: 2 / 3;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@media (min-width: 768px) and (max-width: 49.99rem) {
|
||||
@@ -169,8 +164,6 @@
|
||||
grid-column: 1 / 2;
|
||||
grid-row: 2 / 3;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
@@ -212,14 +205,20 @@
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
padding: 1px;
|
||||
background: radial-gradient(200px circle at var(--glow-x) var(--glow-y),
|
||||
background: radial-gradient(
|
||||
200px circle at var(--glow-x) var(--glow-y),
|
||||
rgba(132, 0, 255, calc(var(--glow-intensity) * 0.8)) 0%,
|
||||
rgba(132, 0, 255, calc(var(--glow-intensity) * 0.4)) 30%,
|
||||
transparent 60%);
|
||||
transparent 60%
|
||||
);
|
||||
border-radius: inherit;
|
||||
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
||||
mask:
|
||||
linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
mask-composite: subtract;
|
||||
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
||||
-webkit-mask:
|
||||
linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s ease;
|
||||
@@ -230,8 +229,6 @@
|
||||
background: #07160a;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.feature-card:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -314,7 +311,9 @@
|
||||
}
|
||||
|
||||
.feature-card.particle-container:hover {
|
||||
box-shadow: 0 4px 20px rgba(24, 78, 42, 0.4), 0 0 30px rgba(0, 255, 76, 0.2);
|
||||
box-shadow:
|
||||
0 4px 20px rgba(24, 78, 42, 0.4),
|
||||
0 0 30px rgba(0, 255, 76, 0.2);
|
||||
background: #07160b;
|
||||
}
|
||||
|
||||
@@ -481,8 +480,6 @@
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@media (max-width: 479px) {
|
||||
.features-section {
|
||||
padding: 4rem 1rem 2rem;
|
||||
@@ -510,8 +507,6 @@
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.feature-card h3 {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
@@ -553,8 +548,6 @@
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.feature-card h3 {
|
||||
font-size: 0.95rem;
|
||||
margin-bottom: 0.4rem;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<div class="features-container">
|
||||
<div class="features-header">
|
||||
<h3 class="features-title">Zero cost, all the cool.</h3>
|
||||
|
||||
<p class="features-subtitle">Everything you need to add flair to your websites</p>
|
||||
</div>
|
||||
|
||||
@@ -13,11 +14,16 @@
|
||||
<div className="messages-gif-wrapper">
|
||||
<img src="/assets/messages.gif" alt="Messages animation" className="messages-gif" />
|
||||
</div>
|
||||
|
||||
<h2>
|
||||
<template v-if="isMobile">100</template>
|
||||
<CountUp v-else :to="100" />%
|
||||
|
||||
<CountUp v-else :to="100" />
|
||||
%
|
||||
</h2>
|
||||
|
||||
<h3>Free & Open Source</h3>
|
||||
|
||||
<p>Loved by developers around the world</p>
|
||||
</ParticleCard>
|
||||
|
||||
@@ -25,19 +31,24 @@
|
||||
<div className="components-gif-wrapper">
|
||||
<img src="/assets/components.gif" alt="Components animation" className="components-gif" />
|
||||
</div>
|
||||
|
||||
<h2>
|
||||
<template v-if="isMobile">40</template>
|
||||
<CountUp v-else :to="40" />+
|
||||
|
||||
<CountUp v-else :to="40" />
|
||||
+
|
||||
</h2>
|
||||
|
||||
<h3>Curated Components</h3>
|
||||
|
||||
<p>Growing weekly & only getting better</p>
|
||||
</ParticleCard>
|
||||
|
||||
<ParticleCard class="feature-card card4" :disable-animations="isMobile">
|
||||
<h2>
|
||||
Modern
|
||||
</h2>
|
||||
<h2>Modern</h2>
|
||||
|
||||
<h3>Technologies</h3>
|
||||
|
||||
<p>TypeScript + Tailwind, ready to ship</p>
|
||||
</ParticleCard>
|
||||
</div>
|
||||
@@ -46,26 +57,26 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, defineComponent, h } from 'vue'
|
||||
import { gsap } from 'gsap'
|
||||
import CountUp from '../../../content/Animations/CountUp/CountUp.vue'
|
||||
import './FeatureCards.css'
|
||||
import { ref, onMounted, onUnmounted, defineComponent, h } from 'vue';
|
||||
import { gsap } from 'gsap';
|
||||
import CountUp from '../../../content/Animations/CountUp/CountUp.vue';
|
||||
import './FeatureCards.css';
|
||||
|
||||
const isMobile = ref(false)
|
||||
const gridRef = ref<HTMLDivElement | null>(null)
|
||||
const isMobile = ref(false);
|
||||
const gridRef = ref<HTMLDivElement | null>(null);
|
||||
|
||||
const checkIsMobile = () => {
|
||||
isMobile.value = window.innerWidth <= 768
|
||||
}
|
||||
isMobile.value = window.innerWidth <= 768;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
checkIsMobile()
|
||||
window.addEventListener('resize', checkIsMobile)
|
||||
})
|
||||
checkIsMobile();
|
||||
window.addEventListener('resize', checkIsMobile);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', checkIsMobile)
|
||||
})
|
||||
window.removeEventListener('resize', checkIsMobile);
|
||||
});
|
||||
|
||||
const ParticleCard = defineComponent({
|
||||
name: 'ParticleCard',
|
||||
@@ -76,114 +87,119 @@ const ParticleCard = defineComponent({
|
||||
}
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
const cardRef = ref<HTMLDivElement | null>(null)
|
||||
const particlesRef = ref<HTMLDivElement[]>([])
|
||||
const timeoutsRef = ref<number[]>([])
|
||||
const isHoveredRef = ref(false)
|
||||
const memoizedParticles = ref<HTMLDivElement[]>([])
|
||||
const particlesInit = ref(false)
|
||||
const cardRef = ref<HTMLDivElement | null>(null);
|
||||
const particlesRef = ref<HTMLDivElement[]>([]);
|
||||
const timeoutsRef = ref<number[]>([]);
|
||||
const isHoveredRef = ref(false);
|
||||
const memoizedParticles = ref<HTMLDivElement[]>([]);
|
||||
const particlesInit = ref(false);
|
||||
|
||||
const createParticle = (x: number, y: number): HTMLDivElement => {
|
||||
const el = document.createElement('div')
|
||||
el.className = 'particle'
|
||||
const el = document.createElement('div');
|
||||
el.className = 'particle';
|
||||
el.style.cssText = `
|
||||
position:absolute;width:4px;height:4px;border-radius:50%;
|
||||
background:rgba(132,0,255,1);box-shadow:0 0 6px rgba(132,0,255,.6);
|
||||
pointer-events:none;z-index:100;left:${x}px;top:${y}px;
|
||||
`
|
||||
return el
|
||||
}
|
||||
`;
|
||||
return el;
|
||||
};
|
||||
|
||||
const memoizeParticles = () => {
|
||||
if (particlesInit.value || !cardRef.value) return
|
||||
const { width, height } = cardRef.value.getBoundingClientRect()
|
||||
if (particlesInit.value || !cardRef.value) return;
|
||||
const { width, height } = cardRef.value.getBoundingClientRect();
|
||||
Array.from({ length: 12 }).forEach(() => {
|
||||
memoizedParticles.value.push(createParticle(Math.random() * width, Math.random() * height))
|
||||
})
|
||||
particlesInit.value = true
|
||||
}
|
||||
memoizedParticles.value.push(createParticle(Math.random() * width, Math.random() * height));
|
||||
});
|
||||
particlesInit.value = true;
|
||||
};
|
||||
|
||||
const clearParticles = () => {
|
||||
timeoutsRef.value.forEach(clearTimeout)
|
||||
timeoutsRef.value = []
|
||||
timeoutsRef.value.forEach(clearTimeout);
|
||||
timeoutsRef.value = [];
|
||||
particlesRef.value.forEach(p =>
|
||||
gsap.to(p, {
|
||||
scale: 0,
|
||||
opacity: 0,
|
||||
duration: 0.3,
|
||||
ease: "back.in(1.7)",
|
||||
ease: 'back.in(1.7)',
|
||||
onComplete: () => {
|
||||
if (p.parentNode) {
|
||||
p.parentNode.removeChild(p)
|
||||
p.parentNode.removeChild(p);
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
)
|
||||
particlesRef.value = []
|
||||
}
|
||||
);
|
||||
particlesRef.value = [];
|
||||
};
|
||||
|
||||
const animateParticles = () => {
|
||||
if (!cardRef.value || !isHoveredRef.value) return
|
||||
if (!particlesInit.value) memoizeParticles()
|
||||
if (!cardRef.value || !isHoveredRef.value) return;
|
||||
if (!particlesInit.value) memoizeParticles();
|
||||
|
||||
memoizedParticles.value.forEach((particle, i) => {
|
||||
const id = setTimeout(() => {
|
||||
if (!isHoveredRef.value || !cardRef.value) return
|
||||
const clone = particle.cloneNode(true) as HTMLDivElement
|
||||
cardRef.value.appendChild(clone)
|
||||
particlesRef.value.push(clone)
|
||||
if (!isHoveredRef.value || !cardRef.value) return;
|
||||
const clone = particle.cloneNode(true) as HTMLDivElement;
|
||||
cardRef.value.appendChild(clone);
|
||||
particlesRef.value.push(clone);
|
||||
|
||||
gsap.set(clone, { scale: 0, opacity: 0 })
|
||||
gsap.to(clone, { scale: 1, opacity: 1, duration: 0.3, ease: "back.out(1.7)" })
|
||||
gsap.set(clone, { scale: 0, opacity: 0 });
|
||||
gsap.to(clone, { scale: 1, opacity: 1, duration: 0.3, ease: 'back.out(1.7)' });
|
||||
gsap.to(clone, {
|
||||
x: (Math.random() - 0.5) * 100,
|
||||
y: (Math.random() - 0.5) * 100,
|
||||
rotation: Math.random() * 360,
|
||||
duration: 2 + Math.random() * 2,
|
||||
ease: "none",
|
||||
ease: 'none',
|
||||
repeat: -1,
|
||||
yoyo: true,
|
||||
})
|
||||
gsap.to(clone, { opacity: 0.3, duration: 1.5, ease: "power2.inOut", repeat: -1, yoyo: true })
|
||||
}, i * 100)
|
||||
timeoutsRef.value.push(id)
|
||||
})
|
||||
}
|
||||
yoyo: true
|
||||
});
|
||||
gsap.to(clone, { opacity: 0.3, duration: 1.5, ease: 'power2.inOut', repeat: -1, yoyo: true });
|
||||
}, i * 100);
|
||||
timeoutsRef.value.push(id);
|
||||
});
|
||||
};
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
isHoveredRef.value = true
|
||||
animateParticles()
|
||||
}
|
||||
isHoveredRef.value = true;
|
||||
animateParticles();
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
isHoveredRef.value = false
|
||||
clearParticles()
|
||||
}
|
||||
isHoveredRef.value = false;
|
||||
clearParticles();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (props.disableAnimations || !cardRef.value) return
|
||||
if (props.disableAnimations || !cardRef.value) return;
|
||||
|
||||
const node = cardRef.value
|
||||
node.addEventListener('mouseenter', handleMouseEnter)
|
||||
node.addEventListener('mouseleave', handleMouseLeave)
|
||||
})
|
||||
const node = cardRef.value;
|
||||
node.addEventListener('mouseenter', handleMouseEnter);
|
||||
node.addEventListener('mouseleave', handleMouseLeave);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (cardRef.value) {
|
||||
cardRef.value.removeEventListener('mouseenter', handleMouseEnter)
|
||||
cardRef.value.removeEventListener('mouseleave', handleMouseLeave)
|
||||
cardRef.value.removeEventListener('mouseenter', handleMouseEnter);
|
||||
cardRef.value.removeEventListener('mouseleave', handleMouseLeave);
|
||||
}
|
||||
isHoveredRef.value = false
|
||||
clearParticles()
|
||||
})
|
||||
isHoveredRef.value = false;
|
||||
clearParticles();
|
||||
});
|
||||
|
||||
return () => h('div', {
|
||||
return () =>
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
ref: cardRef,
|
||||
class: 'particle-container',
|
||||
style: { position: 'relative', overflow: 'hidden' }
|
||||
}, slots.default?.())
|
||||
},
|
||||
slots.default?.()
|
||||
);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const GlobalSpotlight = defineComponent({
|
||||
name: 'GlobalSpotlight',
|
||||
@@ -198,89 +214,88 @@ const GlobalSpotlight = defineComponent({
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const spotlightRef = ref<HTMLDivElement | null>(null)
|
||||
const isInsideSectionRef = ref(false)
|
||||
const spotlightRef = ref<HTMLDivElement | null>(null);
|
||||
const isInsideSectionRef = ref(false);
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
if (!spotlightRef.value || !props.gridRef.value) return
|
||||
const section = props.gridRef.value.closest('.features-section')
|
||||
const rect = section?.getBoundingClientRect()
|
||||
if (!spotlightRef.value || !props.gridRef.value) return;
|
||||
const section = props.gridRef.value.closest('.features-section');
|
||||
const rect = section?.getBoundingClientRect();
|
||||
const inside =
|
||||
rect &&
|
||||
e.clientX >= rect.left && e.clientX <= rect.right &&
|
||||
e.clientY >= rect.top && e.clientY <= rect.bottom
|
||||
rect && e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom;
|
||||
|
||||
isInsideSectionRef.value = inside
|
||||
const cards = props.gridRef.value.querySelectorAll('.feature-card')
|
||||
isInsideSectionRef.value = inside;
|
||||
const cards = props.gridRef.value.querySelectorAll('.feature-card');
|
||||
|
||||
if (!inside) {
|
||||
gsap.to(spotlightRef.value, { opacity: 0, duration: 0.3, ease: "power2.out" })
|
||||
cards.forEach((card: HTMLElement) => card.style.setProperty('--glow-intensity', '0'))
|
||||
return
|
||||
gsap.to(spotlightRef.value, { opacity: 0, duration: 0.3, ease: 'power2.out' });
|
||||
cards.forEach((card: HTMLElement) => card.style.setProperty('--glow-intensity', '0'));
|
||||
return;
|
||||
}
|
||||
|
||||
let minDist = Infinity
|
||||
const prox = 100, fade = 150
|
||||
let minDist = Infinity;
|
||||
const prox = 100,
|
||||
fade = 150;
|
||||
cards.forEach((card: HTMLElement) => {
|
||||
const r = card.getBoundingClientRect()
|
||||
const cx = r.left + r.width / 2
|
||||
const cy = r.top + r.height / 2
|
||||
const d = Math.hypot(e.clientX - cx, e.clientY - cy) - Math.max(r.width, r.height) / 2
|
||||
const ed = Math.max(0, d)
|
||||
minDist = Math.min(minDist, ed)
|
||||
const r = card.getBoundingClientRect();
|
||||
const cx = r.left + r.width / 2;
|
||||
const cy = r.top + r.height / 2;
|
||||
const d = Math.hypot(e.clientX - cx, e.clientY - cy) - Math.max(r.width, r.height) / 2;
|
||||
const ed = Math.max(0, d);
|
||||
minDist = Math.min(minDist, ed);
|
||||
|
||||
const rx = ((e.clientX - r.left) / r.width) * 100
|
||||
const ry = ((e.clientY - r.top) / r.height) * 100
|
||||
let glow = 0
|
||||
if (ed <= prox) glow = 1
|
||||
else if (ed <= fade) glow = (fade - ed) / (fade - prox)
|
||||
card.style.setProperty('--glow-x', `${rx}%`)
|
||||
card.style.setProperty('--glow-y', `${ry}%`)
|
||||
card.style.setProperty('--glow-intensity', String(glow))
|
||||
})
|
||||
const rx = ((e.clientX - r.left) / r.width) * 100;
|
||||
const ry = ((e.clientY - r.top) / r.height) * 100;
|
||||
let glow = 0;
|
||||
if (ed <= prox) glow = 1;
|
||||
else if (ed <= fade) glow = (fade - ed) / (fade - prox);
|
||||
card.style.setProperty('--glow-x', `${rx}%`);
|
||||
card.style.setProperty('--glow-y', `${ry}%`);
|
||||
card.style.setProperty('--glow-intensity', String(glow));
|
||||
});
|
||||
|
||||
gsap.to(spotlightRef.value, { left: e.clientX, top: e.clientY, duration: 0.1, ease: "power2.out" })
|
||||
const target = minDist <= prox ? 0.8 : minDist <= fade ? ((fade - minDist) / (fade - prox)) * 0.8 : 0
|
||||
gsap.to(spotlightRef.value, { opacity: target, duration: target > 0 ? 0.2 : 0.5, ease: "power2.out" })
|
||||
}
|
||||
gsap.to(spotlightRef.value, { left: e.clientX, top: e.clientY, duration: 0.1, ease: 'power2.out' });
|
||||
const target = minDist <= prox ? 0.8 : minDist <= fade ? ((fade - minDist) / (fade - prox)) * 0.8 : 0;
|
||||
gsap.to(spotlightRef.value, { opacity: target, duration: target > 0 ? 0.2 : 0.5, ease: 'power2.out' });
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
isInsideSectionRef.value = false
|
||||
isInsideSectionRef.value = false;
|
||||
props.gridRef.value
|
||||
?.querySelectorAll('.feature-card')
|
||||
.forEach((card: HTMLElement) => card.style.setProperty('--glow-intensity', '0'))
|
||||
.forEach((card: HTMLElement) => card.style.setProperty('--glow-intensity', '0'));
|
||||
if (spotlightRef.value) {
|
||||
gsap.to(spotlightRef.value, { opacity: 0, duration: 0.3, ease: "power2.out" })
|
||||
}
|
||||
gsap.to(spotlightRef.value, { opacity: 0, duration: 0.3, ease: 'power2.out' });
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (props.disableAnimations || !props.gridRef?.value) return
|
||||
if (props.disableAnimations || !props.gridRef?.value) return;
|
||||
|
||||
const spotlight = document.createElement('div')
|
||||
spotlight.className = 'global-spotlight'
|
||||
const spotlight = document.createElement('div');
|
||||
spotlight.className = 'global-spotlight';
|
||||
spotlight.style.cssText = `
|
||||
position:fixed;width:800px;height:800px;border-radius:50%;pointer-events:none;
|
||||
background:radial-gradient(circle,rgba(132,0,255,.15) 0%,rgba(132,0,255,.08) 15%,
|
||||
rgba(132,0,255,.04) 25%,rgba(132,0,255,.02) 40%,rgba(132,0,255,.01) 65%,transparent 70%);
|
||||
z-index:200;opacity:0;transform:translate(-50%,-50%);mix-blend-mode:screen;
|
||||
`
|
||||
document.body.appendChild(spotlight)
|
||||
spotlightRef.value = spotlight
|
||||
`;
|
||||
document.body.appendChild(spotlight);
|
||||
spotlightRef.value = spotlight;
|
||||
|
||||
document.addEventListener('mousemove', handleMouseMove)
|
||||
document.addEventListener('mouseleave', handleMouseLeave)
|
||||
})
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
document.addEventListener('mouseleave', handleMouseLeave);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('mousemove', handleMouseMove)
|
||||
document.removeEventListener('mouseleave', handleMouseLeave)
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('mouseleave', handleMouseLeave);
|
||||
if (spotlightRef.value?.parentNode) {
|
||||
spotlightRef.value.parentNode.removeChild(spotlightRef.value)
|
||||
spotlightRef.value.parentNode.removeChild(spotlightRef.value);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
return () => null
|
||||
return () => null;
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
@@ -48,13 +48,13 @@
|
||||
}
|
||||
|
||||
.footer-heart {
|
||||
color: #27FF64;
|
||||
color: #27ff64;
|
||||
font-size: 1em;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.footer-creator-link {
|
||||
color: #27FF64;
|
||||
color: #27ff64;
|
||||
text-decoration: none;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,14 @@
|
||||
<div class="footer-content">
|
||||
<div class="footer-left">
|
||||
<img :src="vueBitsLogo" alt="Vue Bits" class="footer-logo" />
|
||||
|
||||
<p class="footer-description">
|
||||
A library created with <i class="pi pi-heart-fill footer-heart"></i> by
|
||||
A library created with
|
||||
<i class="pi pi-heart-fill footer-heart"></i>
|
||||
by
|
||||
<a href="https://davidhaz.com/" target="_blank" class="footer-creator-link">this guy</a>
|
||||
</p>
|
||||
|
||||
<p class="footer-copyright">© {{ currentYear }} Vue Bits</p>
|
||||
</div>
|
||||
|
||||
@@ -15,15 +19,12 @@
|
||||
<a href="https://github.com/DavidHDev/vue-bits" target="_blank" rel="noopener noreferrer" class="footer-link">
|
||||
GitHub
|
||||
</a>
|
||||
<router-link to="/text-animations/split-text" class="footer-link">
|
||||
Docs
|
||||
</router-link>
|
||||
<a href="https://www.jsrepo.com/" target="_blank" class="footer-link">
|
||||
CLI
|
||||
</a>
|
||||
<a href="https://reactbits.dev/" target="_blank" class="footer-link">
|
||||
React Bits
|
||||
</a>
|
||||
|
||||
<router-link to="/text-animations/split-text" class="footer-link">Docs</router-link>
|
||||
|
||||
<a href="https://www.jsrepo.com/" target="_blank" class="footer-link">CLI</a>
|
||||
|
||||
<a href="https://reactbits.dev/" target="_blank" class="footer-link">React Bits</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
@@ -31,10 +32,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import vueBitsLogo from '../../../assets/logos/vue-bits-logo.svg'
|
||||
import FadeContent from '@/content/Animations/FadeContent/FadeContent.vue'
|
||||
import './Footer.css'
|
||||
import { computed } from 'vue';
|
||||
import vueBitsLogo from '../../../assets/logos/vue-bits-logo.svg';
|
||||
import FadeContent from '@/content/Animations/FadeContent/FadeContent.vue';
|
||||
import './Footer.css';
|
||||
|
||||
const currentYear = computed(() => new Date().getFullYear())
|
||||
const currentYear = computed(() => new Date().getFullYear());
|
||||
</script>
|
||||
@@ -2,18 +2,41 @@
|
||||
<div class="landing-content">
|
||||
<div class="hero-main-content">
|
||||
<h1 class="landing-title">
|
||||
<ResponsiveSplitText :is-mobile="isMobile" text="Animated Vue components" class-name="hero-split"
|
||||
split-type="chars" :delay="30" :duration="2" ease="elastic.out(0.5, 0.3)" />
|
||||
<ResponsiveSplitText
|
||||
:is-mobile="isMobile"
|
||||
text="Animated Vue components"
|
||||
class-name="hero-split"
|
||||
split-type="chars"
|
||||
:delay="30"
|
||||
:duration="2"
|
||||
ease="elastic.out(0.5, 0.3)"
|
||||
/>
|
||||
|
||||
<br />
|
||||
<ResponsiveSplitText :is-mobile="isMobile" text="for creative developers" class-name="hero-split"
|
||||
split-type="chars" :delay="30" :duration="2" ease="elastic.out(0.5, 0.3)" />
|
||||
|
||||
<ResponsiveSplitText
|
||||
:is-mobile="isMobile"
|
||||
text="for creative developers"
|
||||
class-name="hero-split"
|
||||
split-type="chars"
|
||||
:delay="30"
|
||||
:duration="2"
|
||||
ease="elastic.out(0.5, 0.3)"
|
||||
/>
|
||||
</h1>
|
||||
|
||||
<ResponsiveSplitText :is-mobile="isMobile" class-name="landing-subtitle" split-type="words" :delay="10"
|
||||
:duration="1" text="Eighty-plus snippets, ready to be dropped into your Vue projects" />
|
||||
<ResponsiveSplitText
|
||||
:is-mobile="isMobile"
|
||||
class-name="landing-subtitle"
|
||||
split-type="words"
|
||||
:delay="10"
|
||||
:duration="1"
|
||||
text="Eighty-plus snippets, ready to be dropped into your Vue projects"
|
||||
/>
|
||||
|
||||
<router-link to="/text-animations/split-text" class="landing-button">
|
||||
<span>Browse Components</span>
|
||||
|
||||
<div class="button-arrow-circle">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="#ffffff" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 12L10 8L6 4" stroke="#0b0b0b" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
@@ -25,8 +48,14 @@
|
||||
<div v-if="!isMobile" class="hero-cards-container">
|
||||
<div class="hero-card hero-card-1" @click="openUrl('https://vue-bits.dev/backgrounds/dot-grid')">
|
||||
<div class="w-full h-full relative hero-dot-grid">
|
||||
<DotGrid base-color="#ffffff" active-color="rgba(138, 43, 226, 0.9)" :dot-size="8" :gap="16"
|
||||
:proximity="50" />
|
||||
<DotGrid
|
||||
base-color="#ffffff"
|
||||
active-color="rgba(138, 43, 226, 0.9)"
|
||||
:dot-size="8"
|
||||
:gap="16"
|
||||
:proximity="50"
|
||||
/>
|
||||
|
||||
<div class="placeholder-card"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -34,11 +63,13 @@
|
||||
<div class="hero-cards-row">
|
||||
<div class="hero-card hero-card-2" @click="openUrl('https://vue-bits.dev/backgrounds/letter-glitch')">
|
||||
<LetterGlitch class-name="hero-glitch" :glitch-colors="['#ffffff', '#999999', '#333333']" />
|
||||
|
||||
<div class="placeholder-card"></div>
|
||||
</div>
|
||||
|
||||
<div class="hero-card hero-card-3" @click="openUrl('https://vue-bits.dev/backgrounds/squares')">
|
||||
<Squares border-color="#fff" :speed="0.2" direction="diagonal" hover-fill-color="#fff" />
|
||||
|
||||
<div class="placeholder-card"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -47,11 +78,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, h, defineComponent } from 'vue'
|
||||
import DotGrid from '@/content/Backgrounds/DotGrid/DotGrid.vue'
|
||||
import SplitText from '@/content/TextAnimations/SplitText/SplitText.vue'
|
||||
import LetterGlitch from '@/content/Backgrounds/LetterGlitch/LetterGlitch.vue'
|
||||
import Squares from '@/content/Backgrounds/Squares/Squares.vue'
|
||||
import { ref, onMounted, onUnmounted, h, defineComponent } from 'vue';
|
||||
import DotGrid from '@/content/Backgrounds/DotGrid/DotGrid.vue';
|
||||
import SplitText from '@/content/TextAnimations/SplitText/SplitText.vue';
|
||||
import LetterGlitch from '@/content/Backgrounds/LetterGlitch/LetterGlitch.vue';
|
||||
import Squares from '@/content/Backgrounds/Squares/Squares.vue';
|
||||
|
||||
const ResponsiveSplitText = defineComponent({
|
||||
props: {
|
||||
@@ -71,7 +102,7 @@ const ResponsiveSplitText = defineComponent({
|
||||
},
|
||||
render() {
|
||||
if (this.isMobile) {
|
||||
return h('span', { class: this.className }, this.text)
|
||||
return h('span', { class: this.className }, this.text);
|
||||
} else {
|
||||
return h(SplitText, {
|
||||
text: this.text,
|
||||
@@ -86,29 +117,29 @@ const ResponsiveSplitText = defineComponent({
|
||||
rootMargin: this.rootMargin,
|
||||
textAlign: this.textAlign,
|
||||
onLetterAnimationComplete: this.onLetterAnimationComplete as (() => void) | undefined
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const openUrl = (url: string) => {
|
||||
window.open(url)
|
||||
}
|
||||
window.open(url);
|
||||
};
|
||||
|
||||
const isMobile = ref(false)
|
||||
const isMobile = ref(false);
|
||||
|
||||
const checkIsMobile = () => {
|
||||
isMobile.value = window.innerWidth <= 768
|
||||
}
|
||||
isMobile.value = window.innerWidth <= 768;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
checkIsMobile()
|
||||
window.addEventListener('resize', checkIsMobile)
|
||||
})
|
||||
checkIsMobile();
|
||||
window.addEventListener('resize', checkIsMobile);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', checkIsMobile)
|
||||
})
|
||||
window.removeEventListener('resize', checkIsMobile);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,29 +1,32 @@
|
||||
<template>
|
||||
<div v-if="!isMobile" ref="containerRef" :style="{
|
||||
<div
|
||||
v-if="!isMobile"
|
||||
ref="containerRef"
|
||||
:style="{
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
overflow: 'hidden',
|
||||
width: '100vw',
|
||||
height: '100vh'
|
||||
}">
|
||||
</div>
|
||||
}"
|
||||
></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { Renderer, Camera, Transform, Program, Mesh, Geometry } from 'ogl'
|
||||
import { ref, onMounted, onUnmounted, watch } from 'vue';
|
||||
import { Renderer, Camera, Transform, Program, Mesh, Geometry } from 'ogl';
|
||||
|
||||
interface Props {
|
||||
xOffset?: number
|
||||
yOffset?: number
|
||||
rotationDeg?: number
|
||||
focalLength?: number
|
||||
speed1?: number
|
||||
speed2?: number
|
||||
dir2?: number
|
||||
bend1?: number
|
||||
bend2?: number
|
||||
fadeInDuration?: number
|
||||
xOffset?: number;
|
||||
yOffset?: number;
|
||||
rotationDeg?: number;
|
||||
focalLength?: number;
|
||||
speed1?: number;
|
||||
speed2?: number;
|
||||
dir2?: number;
|
||||
bend1?: number;
|
||||
bend2?: number;
|
||||
fadeInDuration?: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -37,7 +40,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
bend1: 0.9,
|
||||
bend2: 0.6,
|
||||
fadeInDuration: 2000
|
||||
})
|
||||
});
|
||||
|
||||
const vertex = /* glsl */ `
|
||||
attribute vec2 position;
|
||||
@@ -46,7 +49,7 @@ void main() {
|
||||
vUv = position * 0.5 + 0.5;
|
||||
gl_Position = vec4(position, 0.0, 1.0);
|
||||
}
|
||||
`
|
||||
`;
|
||||
|
||||
const fragment = /* glsl */ `
|
||||
precision mediump float;
|
||||
@@ -143,40 +146,40 @@ void main() {
|
||||
mainImage(color, coord);
|
||||
gl_FragColor = color;
|
||||
}
|
||||
`
|
||||
`;
|
||||
|
||||
const isMobile = ref(false)
|
||||
const isVisible = ref(true)
|
||||
const containerRef = ref<HTMLDivElement | null>(null)
|
||||
const uniformOffset = ref(new Float32Array([props.xOffset, props.yOffset]))
|
||||
const uniformResolution = ref(new Float32Array([1, 1]))
|
||||
const rendererRef = ref<Renderer | null>(null)
|
||||
const fadeStartTime = ref<number | null>(null)
|
||||
const lastTimeRef = ref(0)
|
||||
const pausedTimeRef = ref(0)
|
||||
const rafId = ref<number | null>(null)
|
||||
const resizeObserver = ref<ResizeObserver | null>(null)
|
||||
const intersectionObserver = ref<IntersectionObserver | null>(null)
|
||||
const isMobile = ref(false);
|
||||
const isVisible = ref(true);
|
||||
const containerRef = ref<HTMLDivElement | null>(null);
|
||||
const uniformOffset = ref(new Float32Array([props.xOffset, props.yOffset]));
|
||||
const uniformResolution = ref(new Float32Array([1, 1]));
|
||||
const rendererRef = ref<Renderer | null>(null);
|
||||
const fadeStartTime = ref<number | null>(null);
|
||||
const lastTimeRef = ref(0);
|
||||
const pausedTimeRef = ref(0);
|
||||
const rafId = ref<number | null>(null);
|
||||
const resizeObserver = ref<ResizeObserver | null>(null);
|
||||
const intersectionObserver = ref<IntersectionObserver | null>(null);
|
||||
|
||||
const checkIsMobile = () => {
|
||||
isMobile.value = window.innerWidth <= 768
|
||||
}
|
||||
isMobile.value = window.innerWidth <= 768;
|
||||
};
|
||||
|
||||
const resize = () => {
|
||||
if (!containerRef.value || !rendererRef.value) return
|
||||
if (!containerRef.value || !rendererRef.value) return;
|
||||
|
||||
const { width, height } = containerRef.value.getBoundingClientRect()
|
||||
rendererRef.value.setSize(width, height)
|
||||
uniformResolution.value[0] = width * rendererRef.value.dpr
|
||||
uniformResolution.value[1] = height * rendererRef.value.dpr
|
||||
const { width, height } = containerRef.value.getBoundingClientRect();
|
||||
rendererRef.value.setSize(width, height);
|
||||
uniformResolution.value[0] = width * rendererRef.value.dpr;
|
||||
uniformResolution.value[1] = height * rendererRef.value.dpr;
|
||||
|
||||
const gl = rendererRef.value.gl
|
||||
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight)
|
||||
gl.clear(gl.COLOR_BUFFER_BIT)
|
||||
}
|
||||
const gl = rendererRef.value.gl;
|
||||
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
};
|
||||
|
||||
const initWebGL = () => {
|
||||
if (isMobile.value || !containerRef.value) return
|
||||
if (isMobile.value || !containerRef.value) return;
|
||||
|
||||
const renderer = new Renderer({
|
||||
alpha: true,
|
||||
@@ -184,20 +187,20 @@ const initWebGL = () => {
|
||||
antialias: false,
|
||||
depth: false,
|
||||
stencil: false,
|
||||
powerPreference: 'high-performance',
|
||||
})
|
||||
rendererRef.value = renderer
|
||||
powerPreference: 'high-performance'
|
||||
});
|
||||
rendererRef.value = renderer;
|
||||
|
||||
const gl = renderer.gl
|
||||
gl.clearColor(0, 0, 0, 0)
|
||||
containerRef.value.appendChild(gl.canvas)
|
||||
const gl = renderer.gl;
|
||||
gl.clearColor(0, 0, 0, 0);
|
||||
containerRef.value.appendChild(gl.canvas);
|
||||
|
||||
const camera = new Camera(gl)
|
||||
const scene = new Transform()
|
||||
const camera = new Camera(gl);
|
||||
const scene = new Transform();
|
||||
|
||||
const geometry = new Geometry(gl, {
|
||||
position: { size: 2, data: new Float32Array([-1, -1, 3, -1, -1, 3]) },
|
||||
})
|
||||
position: { size: 2, data: new Float32Array([-1, -1, 3, -1, -1, 3]) }
|
||||
});
|
||||
|
||||
const program = new Program(gl, {
|
||||
vertex,
|
||||
@@ -215,117 +218,117 @@ const initWebGL = () => {
|
||||
bend2: { value: props.bend2 },
|
||||
bendAdj1: { value: 0 },
|
||||
bendAdj2: { value: 0 },
|
||||
uOpacity: { value: 0 },
|
||||
},
|
||||
})
|
||||
new Mesh(gl, { geometry, program }).setParent(scene)
|
||||
uOpacity: { value: 0 }
|
||||
}
|
||||
});
|
||||
new Mesh(gl, { geometry, program }).setParent(scene);
|
||||
|
||||
resize()
|
||||
resize();
|
||||
|
||||
resizeObserver.value = new ResizeObserver(resize)
|
||||
resizeObserver.value.observe(containerRef.value)
|
||||
resizeObserver.value = new ResizeObserver(resize);
|
||||
resizeObserver.value.observe(containerRef.value);
|
||||
|
||||
const loop = (now: number) => {
|
||||
if (isVisible.value) {
|
||||
if (lastTimeRef.value === 0) {
|
||||
lastTimeRef.value = now - pausedTimeRef.value
|
||||
lastTimeRef.value = now - pausedTimeRef.value;
|
||||
}
|
||||
|
||||
const t = (now - lastTimeRef.value) * 0.001
|
||||
const t = (now - lastTimeRef.value) * 0.001;
|
||||
|
||||
if (fadeStartTime.value === null && t > 0.1) {
|
||||
fadeStartTime.value = now
|
||||
fadeStartTime.value = now;
|
||||
}
|
||||
|
||||
let opacity = 0
|
||||
let opacity = 0;
|
||||
if (fadeStartTime.value !== null) {
|
||||
const fadeElapsed = now - fadeStartTime.value
|
||||
opacity = Math.min(fadeElapsed / props.fadeInDuration, 1)
|
||||
opacity = 1 - Math.pow(1 - opacity, 3)
|
||||
const fadeElapsed = now - fadeStartTime.value;
|
||||
opacity = Math.min(fadeElapsed / props.fadeInDuration, 1);
|
||||
opacity = 1 - Math.pow(1 - opacity, 3);
|
||||
}
|
||||
|
||||
uniformOffset.value[0] = props.xOffset
|
||||
uniformOffset.value[1] = props.yOffset
|
||||
uniformOffset.value[0] = props.xOffset;
|
||||
uniformOffset.value[1] = props.yOffset;
|
||||
|
||||
program.uniforms.iTime.value = t
|
||||
program.uniforms.uRotation.value = props.rotationDeg * Math.PI / 180
|
||||
program.uniforms.focalLength.value = props.focalLength
|
||||
program.uniforms.uOpacity.value = opacity
|
||||
program.uniforms.iTime.value = t;
|
||||
program.uniforms.uRotation.value = (props.rotationDeg * Math.PI) / 180;
|
||||
program.uniforms.focalLength.value = props.focalLength;
|
||||
program.uniforms.uOpacity.value = opacity;
|
||||
|
||||
renderer.render({ scene, camera })
|
||||
renderer.render({ scene, camera });
|
||||
} else {
|
||||
if (lastTimeRef.value !== 0) {
|
||||
pausedTimeRef.value = now - lastTimeRef.value
|
||||
lastTimeRef.value = 0
|
||||
pausedTimeRef.value = now - lastTimeRef.value;
|
||||
lastTimeRef.value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
rafId.value = requestAnimationFrame(loop)
|
||||
}
|
||||
rafId.value = requestAnimationFrame(loop);
|
||||
};
|
||||
|
||||
rafId.value = requestAnimationFrame(loop)
|
||||
}
|
||||
rafId.value = requestAnimationFrame(loop);
|
||||
};
|
||||
|
||||
const setupIntersectionObserver = () => {
|
||||
if (!containerRef.value || isMobile.value) return
|
||||
if (!containerRef.value || isMobile.value) return;
|
||||
|
||||
intersectionObserver.value = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
isVisible.value = entry.isIntersecting
|
||||
isVisible.value = entry.isIntersecting;
|
||||
},
|
||||
{
|
||||
rootMargin: '50px',
|
||||
threshold: 0.1,
|
||||
threshold: 0.1
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
intersectionObserver.value.observe(containerRef.value)
|
||||
}
|
||||
intersectionObserver.value.observe(containerRef.value);
|
||||
};
|
||||
|
||||
const cleanup = () => {
|
||||
if (rafId.value) {
|
||||
cancelAnimationFrame(rafId.value)
|
||||
rafId.value = null
|
||||
cancelAnimationFrame(rafId.value);
|
||||
rafId.value = null;
|
||||
}
|
||||
|
||||
if (resizeObserver.value) {
|
||||
resizeObserver.value.disconnect()
|
||||
resizeObserver.value = null
|
||||
resizeObserver.value.disconnect();
|
||||
resizeObserver.value = null;
|
||||
}
|
||||
|
||||
if (intersectionObserver.value) {
|
||||
intersectionObserver.value.disconnect()
|
||||
intersectionObserver.value = null
|
||||
intersectionObserver.value.disconnect();
|
||||
intersectionObserver.value = null;
|
||||
}
|
||||
|
||||
if (rendererRef.value) {
|
||||
rendererRef.value.gl.canvas.remove()
|
||||
rendererRef.value = null
|
||||
rendererRef.value.gl.canvas.remove();
|
||||
rendererRef.value = null;
|
||||
}
|
||||
|
||||
window.removeEventListener('resize', checkIsMobile)
|
||||
}
|
||||
window.removeEventListener('resize', checkIsMobile);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
checkIsMobile()
|
||||
window.addEventListener('resize', checkIsMobile)
|
||||
checkIsMobile();
|
||||
window.addEventListener('resize', checkIsMobile);
|
||||
|
||||
if (!isMobile.value) {
|
||||
initWebGL()
|
||||
setupIntersectionObserver()
|
||||
initWebGL();
|
||||
setupIntersectionObserver();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
cleanup()
|
||||
})
|
||||
cleanup();
|
||||
});
|
||||
|
||||
watch(isMobile, (newIsMobile) => {
|
||||
watch(isMobile, newIsMobile => {
|
||||
if (newIsMobile) {
|
||||
cleanup()
|
||||
cleanup();
|
||||
} else {
|
||||
initWebGL()
|
||||
setupIntersectionObserver()
|
||||
initWebGL();
|
||||
setupIntersectionObserver();
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
@@ -17,9 +17,7 @@
|
||||
max-width: 1200px;
|
||||
user-select: none;
|
||||
margin: 0 auto;
|
||||
background: linear-gradient(135deg,
|
||||
#3aed6d,
|
||||
rgba(24, 255, 93, 0.6));
|
||||
background: linear-gradient(135deg, #3aed6d, rgba(24, 255, 93, 0.6));
|
||||
background-size: 200% 200%;
|
||||
border-radius: 16px;
|
||||
padding: 4rem 3rem;
|
||||
@@ -78,7 +76,7 @@
|
||||
background: transparent;
|
||||
color: #0b0b0b;
|
||||
border: 2px solid #0b0b0b;
|
||||
padding: .6rem 1.6rem;
|
||||
padding: 0.6rem 1.6rem;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
border-radius: 50px;
|
||||
@@ -88,7 +86,7 @@
|
||||
|
||||
.start-building-button:hover {
|
||||
background: #0b0b0b;
|
||||
color: #27FF64;
|
||||
color: #27ff64;
|
||||
}
|
||||
|
||||
@media (max-width: 1280px) {
|
||||
|
||||
@@ -3,16 +3,15 @@
|
||||
<div class="start-building-container">
|
||||
<div class="start-building-card">
|
||||
<h2 class="start-building-title">Start exploring Vue Bits</h2>
|
||||
|
||||
<p class="start-building-subtitle">Animations, components, backgrounds - it's all here</p>
|
||||
|
||||
<router-link to="/text-animations/split-text" class="start-building-button">
|
||||
Browse Components
|
||||
</router-link>
|
||||
<router-link to="/text-animations/split-text" class="start-building-button">Browse Components</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import './StartBuilding.css'
|
||||
import './StartBuilding.css';
|
||||
</script>
|
||||
@@ -1,20 +1,24 @@
|
||||
<template>
|
||||
<main class="app-container">
|
||||
<Header />
|
||||
|
||||
<section class="category-wrapper">
|
||||
<Sidebar />
|
||||
|
||||
<div class="category-page">
|
||||
<router-view />
|
||||
</div>
|
||||
</section>
|
||||
<Toast position="bottom-right"
|
||||
|
||||
<Toast
|
||||
position="bottom-right"
|
||||
:closeButtonProps="{ style: { right: '0', margin: '0', outline: 'none', border: 'none' } }"
|
||||
:pt="{
|
||||
message: {
|
||||
style: {
|
||||
borderRadius: '10px',
|
||||
border: '1px solid #142216',
|
||||
backgroundColor: '#0b0b0b',
|
||||
backgroundColor: '#0b0b0b'
|
||||
}
|
||||
},
|
||||
messageContent: {
|
||||
@@ -27,7 +31,8 @@
|
||||
display: 'none'
|
||||
}
|
||||
}
|
||||
}" />
|
||||
}"
|
||||
/>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
<div class="drawer-content" @click.stop>
|
||||
<div class="drawer-header">
|
||||
<img :src="Logo" alt="Logo" class="drawer-logo" />
|
||||
|
||||
<button class="close-button" aria-label="Close Menu" @click="closeDrawer">
|
||||
<i class="pi pi-times"></i>
|
||||
</button>
|
||||
@@ -35,6 +36,7 @@
|
||||
|
||||
<div class="drawer-body">
|
||||
<!-- Navigation Categories -->
|
||||
|
||||
<div class="drawer-navigation">
|
||||
<div class="categories-container">
|
||||
<Category
|
||||
@@ -55,9 +57,9 @@
|
||||
|
||||
<div class="drawer-section">
|
||||
<p class="section-title">Useful Links</p>
|
||||
<router-link to="/text-animations/split-text" @click="closeDrawer" class="drawer-link">
|
||||
Docs
|
||||
</router-link>
|
||||
|
||||
<router-link to="/text-animations/split-text" @click="closeDrawer" class="drawer-link">Docs</router-link>
|
||||
|
||||
<a href="https://github.com/DavidHDev/vue-bits" target="_blank" @click="closeDrawer" class="drawer-link">
|
||||
GitHub
|
||||
<i class="pi pi-arrow-up-right arrow-icon"></i>
|
||||
@@ -68,6 +70,7 @@
|
||||
|
||||
<div class="drawer-section">
|
||||
<p class="section-title">Other</p>
|
||||
|
||||
<a href="https://davidhaz.com/" target="_blank" @click="closeDrawer" class="drawer-link">
|
||||
Who made this?
|
||||
<i class="pi pi-arrow-up-right arrow-icon"></i>
|
||||
@@ -80,58 +83,58 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, computed, defineComponent, h } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useStars } from '../../composables/useStars'
|
||||
import { CATEGORIES, NEW, UPDATED } from '../../constants/Categories'
|
||||
import FadeContent from '../../content/Animations/FadeContent/FadeContent.vue'
|
||||
import Logo from '../../assets/logos/vue-bits-logo.svg'
|
||||
import Star from '../../assets/common/star.svg'
|
||||
import { ref, onMounted, onUnmounted, computed, defineComponent, h } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useStars } from '../../composables/useStars';
|
||||
import { CATEGORIES, NEW, UPDATED } from '../../constants/Categories';
|
||||
import FadeContent from '../../content/Animations/FadeContent/FadeContent.vue';
|
||||
import Logo from '../../assets/logos/vue-bits-logo.svg';
|
||||
import Star from '../../assets/common/star.svg';
|
||||
|
||||
const isDrawerOpen = ref(false)
|
||||
const isTransitioning = ref(false)
|
||||
const stars = useStars()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const isDrawerOpen = ref(false);
|
||||
const isTransitioning = ref(false);
|
||||
const stars = useStars();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const slug = (str: string) => str.replace(/\s+/g, "-").toLowerCase()
|
||||
const slug = (str: string) => str.replace(/\s+/g, '-').toLowerCase();
|
||||
|
||||
const toggleDrawer = () => {
|
||||
isDrawerOpen.value = !isDrawerOpen.value
|
||||
}
|
||||
isDrawerOpen.value = !isDrawerOpen.value;
|
||||
};
|
||||
|
||||
const closeDrawer = () => {
|
||||
isDrawerOpen.value = false
|
||||
}
|
||||
isDrawerOpen.value = false;
|
||||
};
|
||||
|
||||
const openGitHub = () => {
|
||||
window.open('https://github.com/DavidHDev/vue-bits', '_blank')
|
||||
}
|
||||
window.open('https://github.com/DavidHDev/vue-bits', '_blank');
|
||||
};
|
||||
|
||||
const onNavClick = () => {
|
||||
closeDrawer()
|
||||
window.scrollTo(0, 0)
|
||||
}
|
||||
closeDrawer();
|
||||
window.scrollTo(0, 0);
|
||||
};
|
||||
|
||||
const handleMobileTransitionNavigation = async (path: string) => {
|
||||
if (isTransitioning.value || route.path === path) return
|
||||
if (isTransitioning.value || route.path === path) return;
|
||||
|
||||
closeDrawer()
|
||||
isTransitioning.value = true
|
||||
closeDrawer();
|
||||
isTransitioning.value = true;
|
||||
|
||||
try {
|
||||
await router.push(path)
|
||||
window.scrollTo(0, 0)
|
||||
await router.push(path);
|
||||
window.scrollTo(0, 0);
|
||||
} finally {
|
||||
isTransitioning.value = false
|
||||
}
|
||||
isTransitioning.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape' && isDrawerOpen.value) {
|
||||
closeDrawer()
|
||||
}
|
||||
closeDrawer();
|
||||
}
|
||||
};
|
||||
|
||||
const Category = defineComponent({
|
||||
name: 'Category',
|
||||
@@ -167,63 +170,67 @@ const Category = defineComponent({
|
||||
},
|
||||
setup(props) {
|
||||
interface ItemType {
|
||||
sub: string
|
||||
path: string
|
||||
isActive: boolean
|
||||
isNew: boolean
|
||||
isUpdated: boolean
|
||||
sub: string;
|
||||
path: string;
|
||||
isActive: boolean;
|
||||
isNew: boolean;
|
||||
isUpdated: boolean;
|
||||
}
|
||||
|
||||
const items = computed(() =>
|
||||
props.category.subcategories.map((sub: string): ItemType => {
|
||||
const path = `/${slug(props.category.name)}/${slug(sub)}`
|
||||
const activePath = props.location.path
|
||||
const path = `/${slug(props.category.name)}/${slug(sub)}`;
|
||||
const activePath = props.location.path;
|
||||
return {
|
||||
sub,
|
||||
path,
|
||||
isActive: activePath === path,
|
||||
isNew: (NEW as string[]).includes(sub),
|
||||
isUpdated: (UPDATED as string[]).includes(sub),
|
||||
}
|
||||
isUpdated: (UPDATED as string[]).includes(sub)
|
||||
};
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return () => h('div', { class: 'category' }, [
|
||||
return () =>
|
||||
h('div', { class: 'category' }, [
|
||||
h('p', { class: 'category-name' }, props.category.name),
|
||||
h('div', { class: 'category-items' },
|
||||
h(
|
||||
'div',
|
||||
{ class: 'category-items' },
|
||||
items.value.map(({ sub, path, isActive, isNew, isUpdated }: ItemType) => {
|
||||
return h('router-link', {
|
||||
return h(
|
||||
'router-link',
|
||||
{
|
||||
key: path,
|
||||
to: path,
|
||||
class: [
|
||||
'sidebar-item',
|
||||
{ 'active-sidebar-item': isActive },
|
||||
{ 'transitioning': props.isTransitioning }
|
||||
],
|
||||
class: ['sidebar-item', { 'active-sidebar-item': isActive }, { transitioning: props.isTransitioning }],
|
||||
onClick: (e: Event) => {
|
||||
e.preventDefault()
|
||||
props.handleTransitionNavigation(path)
|
||||
e.preventDefault();
|
||||
props.handleTransitionNavigation(path);
|
||||
},
|
||||
onMouseenter: (e: Event) => props.onItemMouseEnter(path, e),
|
||||
onMouseleave: props.onItemMouseLeave
|
||||
}, {
|
||||
default: () => [
|
||||
},
|
||||
{
|
||||
default: () =>
|
||||
[
|
||||
sub,
|
||||
isNew ? h('span', { class: 'new-tag' }, 'New') : null,
|
||||
isUpdated ? h('span', { class: 'updated-tag' }, 'Updated') : null
|
||||
].filter(Boolean)
|
||||
})
|
||||
}
|
||||
);
|
||||
})
|
||||
)
|
||||
])
|
||||
]);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('keydown', handleKeyDown)
|
||||
})
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('keydown', handleKeyDown)
|
||||
})
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
});
|
||||
</script>
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<!-- Mobile Drawer -->
|
||||
|
||||
<div v-if="isDrawerOpen" class="drawer-overlay" @click="closeDrawer">
|
||||
<div class="drawer-content" :class="{ 'drawer-open': isDrawerOpen }" @click.stop>
|
||||
<div class="drawer-header sidebar-logo">
|
||||
@@ -7,6 +8,7 @@
|
||||
<router-link to="/" @click="closeDrawer">
|
||||
<img :src="Logo" alt="Logo" class="drawer-logo" />
|
||||
</router-link>
|
||||
|
||||
<button class="icon-button" aria-label="Close" @click="closeDrawer">
|
||||
<i class="pi pi-times"></i>
|
||||
</button>
|
||||
@@ -15,26 +17,41 @@
|
||||
|
||||
<div class="drawer-body">
|
||||
<div class="categories-container">
|
||||
<Category v-for="cat in CATEGORIES" :key="cat.name" :category="cat" :location="route"
|
||||
:pending-active-path="pendingActivePath ?? undefined" :handle-click="onNavClick"
|
||||
:handle-transition-navigation="handleMobileTransitionNavigation" :on-item-mouse-enter="() => { }"
|
||||
:on-item-mouse-leave="() => { }" :is-transitioning="isTransitioning" />
|
||||
<Category
|
||||
v-for="cat in CATEGORIES"
|
||||
:key="cat.name"
|
||||
:category="cat"
|
||||
:location="route"
|
||||
:pending-active-path="pendingActivePath ?? undefined"
|
||||
:handle-click="onNavClick"
|
||||
:handle-transition-navigation="handleMobileTransitionNavigation"
|
||||
:on-item-mouse-enter="() => {}"
|
||||
:on-item-mouse-leave="() => {}"
|
||||
:is-transitioning="isTransitioning"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="separator"></div>
|
||||
|
||||
<div class="useful-links">
|
||||
<p class="useful-links-title">Useful Links</p>
|
||||
|
||||
<div class="links-container">
|
||||
<a href="https://github.com/DavidHDev/vue-bits" target="_blank" @click="closeDrawer" class="useful-link">
|
||||
<span>GitHub</span>
|
||||
|
||||
<i class="pi pi-arrow-up-right arrow-icon"></i>
|
||||
</a>
|
||||
|
||||
<router-link to="/text-animations/split-text" @click="closeDrawer" class="useful-link">
|
||||
<span>Docs</span>
|
||||
|
||||
<i class="pi pi-arrow-up-right arrow-icon"></i>
|
||||
</router-link>
|
||||
|
||||
<a href="https://davidhaz.com/" target="_blank" @click="closeDrawer" class="useful-link">
|
||||
<span>Who made this?</span>
|
||||
|
||||
<i class="pi pi-arrow-up-right arrow-icon"></i>
|
||||
</a>
|
||||
</div>
|
||||
@@ -44,174 +61,192 @@
|
||||
</div>
|
||||
|
||||
<!-- Desktop Sidebar -->
|
||||
<nav ref="sidebarContainerRef" class="sidebar" :class="{ 'sidebar-no-fade': isScrolledToBottom }"
|
||||
@scroll="handleScroll">
|
||||
|
||||
<nav
|
||||
ref="sidebarContainerRef"
|
||||
class="sidebar"
|
||||
:class="{ 'sidebar-no-fade': isScrolledToBottom }"
|
||||
@scroll="handleScroll"
|
||||
>
|
||||
<div ref="sidebarRef" class="sidebar-content">
|
||||
<!-- Active line indicator -->
|
||||
<div class="active-line" :style="{
|
||||
transform: isLineVisible && linePosition !== null
|
||||
? `translateY(${linePosition - 8}px)`
|
||||
: 'translateY(-100px)',
|
||||
|
||||
<div
|
||||
class="active-line"
|
||||
:style="{
|
||||
transform:
|
||||
isLineVisible && linePosition !== null ? `translateY(${linePosition - 8}px)` : 'translateY(-100px)',
|
||||
opacity: isLineVisible ? 1 : 0
|
||||
}"></div>
|
||||
}"
|
||||
></div>
|
||||
|
||||
<!-- Hover line indicator -->
|
||||
<div class="hover-line" :style="{
|
||||
transform: hoverLinePosition !== null
|
||||
? `translateY(${hoverLinePosition - 8}px)`
|
||||
: 'translateY(-100px)',
|
||||
|
||||
<div
|
||||
class="hover-line"
|
||||
:style="{
|
||||
transform: hoverLinePosition !== null ? `translateY(${hoverLinePosition - 8}px)` : 'translateY(-100px)',
|
||||
opacity: isHoverLineVisible ? 1 : 0
|
||||
}"></div>
|
||||
}"
|
||||
></div>
|
||||
|
||||
<div class="categories-list">
|
||||
<Category v-for="cat in CATEGORIES" :key="cat.name" :category="cat" :location="route"
|
||||
:pending-active-path="pendingActivePath ?? undefined" :handle-click="scrollToTop"
|
||||
:handle-transition-navigation="handleTransitionNavigation" :on-item-mouse-enter="onItemEnter"
|
||||
:on-item-mouse-leave="onItemLeave" :is-transitioning="isTransitioning" />
|
||||
<Category
|
||||
v-for="cat in CATEGORIES"
|
||||
:key="cat.name"
|
||||
:category="cat"
|
||||
:location="route"
|
||||
:pending-active-path="pendingActivePath ?? undefined"
|
||||
:handle-click="scrollToTop"
|
||||
:handle-transition-navigation="handleTransitionNavigation"
|
||||
:on-item-mouse-enter="onItemEnter"
|
||||
:on-item-mouse-leave="onItemLeave"
|
||||
:is-transitioning="isTransitioning"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, nextTick, watch, defineComponent, h, computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { CATEGORIES, NEW, UPDATED } from '../../constants/Categories'
|
||||
import Logo from '../../assets/logos/vue-bits-logo.svg'
|
||||
import '../../css/sidebar.css'
|
||||
import { ref, onMounted, onUnmounted, nextTick, watch, defineComponent, h, computed } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { CATEGORIES, NEW, UPDATED } from '../../constants/Categories';
|
||||
import Logo from '../../assets/logos/vue-bits-logo.svg';
|
||||
import '../../css/sidebar.css';
|
||||
|
||||
const HOVER_TIMEOUT_DELAY = 150
|
||||
const HOVER_TIMEOUT_DELAY = 150;
|
||||
|
||||
const isDrawerOpen = ref(false)
|
||||
const linePosition = ref<number | null>(null)
|
||||
const isLineVisible = ref(false)
|
||||
const hoverLinePosition = ref<number | null>(null)
|
||||
const isHoverLineVisible = ref(false)
|
||||
const pendingActivePath = ref<string | null>(null)
|
||||
const isScrolledToBottom = ref(false)
|
||||
const isTransitioning = ref(false)
|
||||
const isDrawerOpen = ref(false);
|
||||
const linePosition = ref<number | null>(null);
|
||||
const isLineVisible = ref(false);
|
||||
const hoverLinePosition = ref<number | null>(null);
|
||||
const isHoverLineVisible = ref(false);
|
||||
const pendingActivePath = ref<string | null>(null);
|
||||
const isScrolledToBottom = ref(false);
|
||||
const isTransitioning = ref(false);
|
||||
|
||||
const sidebarRef = ref<HTMLDivElement>()
|
||||
const sidebarContainerRef = ref<HTMLDivElement>()
|
||||
const sidebarRef = ref<HTMLDivElement>();
|
||||
const sidebarContainerRef = ref<HTMLDivElement>();
|
||||
|
||||
let hoverTimeoutRef: number | null = null
|
||||
let hoverDelayTimeoutRef: number | null = null
|
||||
let hoverTimeoutRef: number | null = null;
|
||||
let hoverDelayTimeoutRef: number | null = null;
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const scrollToTop = () => window.scrollTo(0, 0)
|
||||
const slug = (str: string) => str.replace(/\s+/g, "-").toLowerCase()
|
||||
const scrollToTop = () => window.scrollTo(0, 0);
|
||||
const slug = (str: string) => str.replace(/\s+/g, '-').toLowerCase();
|
||||
|
||||
const findActiveElement = () => {
|
||||
const activePath = pendingActivePath.value || route.path
|
||||
const activePath = pendingActivePath.value || route.path;
|
||||
|
||||
for (const category of CATEGORIES) {
|
||||
const activeItem = category.subcategories.find((sub: string) => {
|
||||
const expectedPath = `/${slug(category.name)}/${slug(sub)}`
|
||||
return activePath === expectedPath
|
||||
})
|
||||
const expectedPath = `/${slug(category.name)}/${slug(sub)}`;
|
||||
return activePath === expectedPath;
|
||||
});
|
||||
if (activeItem) {
|
||||
const selector = `.sidebar a[href="${activePath}"]`
|
||||
const element = document.querySelector(selector) as HTMLElement
|
||||
return element
|
||||
const selector = `.sidebar a[href="${activePath}"]`;
|
||||
const element = document.querySelector(selector) as HTMLElement;
|
||||
return element;
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const updateLinePosition = (el: HTMLElement | null) => {
|
||||
if (!el || !sidebarRef.value || !sidebarRef.value.offsetParent) return null
|
||||
const sidebarRect = sidebarRef.value.getBoundingClientRect()
|
||||
const elRect = el.getBoundingClientRect()
|
||||
return elRect.top - sidebarRect.top + elRect.height / 2
|
||||
}
|
||||
if (!el || !sidebarRef.value || !sidebarRef.value.offsetParent) return null;
|
||||
const sidebarRect = sidebarRef.value.getBoundingClientRect();
|
||||
const elRect = el.getBoundingClientRect();
|
||||
return elRect.top - sidebarRect.top + elRect.height / 2;
|
||||
};
|
||||
|
||||
const closeDrawer = () => {
|
||||
isDrawerOpen.value = false
|
||||
}
|
||||
isDrawerOpen.value = false;
|
||||
};
|
||||
|
||||
const onNavClick = () => {
|
||||
closeDrawer()
|
||||
scrollToTop()
|
||||
}
|
||||
closeDrawer();
|
||||
scrollToTop();
|
||||
};
|
||||
|
||||
const handleTransitionNavigation = async (path: string) => {
|
||||
if (isTransitioning.value || route.path === path) return
|
||||
if (isTransitioning.value || route.path === path) return;
|
||||
|
||||
pendingActivePath.value = path
|
||||
pendingActivePath.value = path;
|
||||
|
||||
// TODO: Implement transition when available
|
||||
await router.push(path)
|
||||
scrollToTop()
|
||||
pendingActivePath.value = null
|
||||
}
|
||||
await router.push(path);
|
||||
scrollToTop();
|
||||
pendingActivePath.value = null;
|
||||
};
|
||||
|
||||
const handleMobileTransitionNavigation = async (path: string) => {
|
||||
if (isTransitioning.value || route.path === path) return
|
||||
if (isTransitioning.value || route.path === path) return;
|
||||
|
||||
closeDrawer()
|
||||
pendingActivePath.value = path
|
||||
closeDrawer();
|
||||
pendingActivePath.value = path;
|
||||
|
||||
// TODO: Implement transition when available
|
||||
await router.push(path)
|
||||
scrollToTop()
|
||||
pendingActivePath.value = null
|
||||
}
|
||||
await router.push(path);
|
||||
scrollToTop();
|
||||
pendingActivePath.value = null;
|
||||
};
|
||||
|
||||
const onItemEnter = (path: string, e: Event) => {
|
||||
if (hoverTimeoutRef) clearTimeout(hoverTimeoutRef)
|
||||
if (hoverDelayTimeoutRef) clearTimeout(hoverDelayTimeoutRef)
|
||||
if (hoverTimeoutRef) clearTimeout(hoverTimeoutRef);
|
||||
if (hoverDelayTimeoutRef) clearTimeout(hoverDelayTimeoutRef);
|
||||
|
||||
const targetElement = e.currentTarget as HTMLElement
|
||||
const pos = updateLinePosition(targetElement)
|
||||
const targetElement = e.currentTarget as HTMLElement;
|
||||
const pos = updateLinePosition(targetElement);
|
||||
|
||||
if (pos !== null) {
|
||||
hoverLinePosition.value = pos
|
||||
hoverLinePosition.value = pos;
|
||||
}
|
||||
|
||||
hoverDelayTimeoutRef = setTimeout(() => {
|
||||
isHoverLineVisible.value = true
|
||||
}, 200)
|
||||
}
|
||||
isHoverLineVisible.value = true;
|
||||
}, 200);
|
||||
};
|
||||
|
||||
const onItemLeave = () => {
|
||||
if (hoverDelayTimeoutRef) clearTimeout(hoverDelayTimeoutRef)
|
||||
if (hoverDelayTimeoutRef) clearTimeout(hoverDelayTimeoutRef);
|
||||
|
||||
hoverTimeoutRef = setTimeout(() => {
|
||||
isHoverLineVisible.value = false
|
||||
}, HOVER_TIMEOUT_DELAY)
|
||||
}
|
||||
isHoverLineVisible.value = false;
|
||||
}, HOVER_TIMEOUT_DELAY);
|
||||
};
|
||||
|
||||
const handleScroll = () => {
|
||||
const sidebarElement = sidebarContainerRef.value
|
||||
if (!sidebarElement) return
|
||||
const sidebarElement = sidebarContainerRef.value;
|
||||
if (!sidebarElement) return;
|
||||
|
||||
const { scrollTop, scrollHeight, clientHeight } = sidebarElement
|
||||
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 10
|
||||
isScrolledToBottom.value = isAtBottom
|
||||
}
|
||||
const { scrollTop, scrollHeight, clientHeight } = sidebarElement;
|
||||
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 10;
|
||||
isScrolledToBottom.value = isAtBottom;
|
||||
};
|
||||
|
||||
const updateActiveLine = async () => {
|
||||
await nextTick()
|
||||
await nextTick();
|
||||
|
||||
setTimeout(() => {
|
||||
const activeEl = findActiveElement()
|
||||
const activeEl = findActiveElement();
|
||||
|
||||
if (!activeEl) {
|
||||
isLineVisible.value = false
|
||||
return
|
||||
isLineVisible.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const pos = updateLinePosition(activeEl)
|
||||
const pos = updateLinePosition(activeEl);
|
||||
if (pos !== null) {
|
||||
linePosition.value = pos
|
||||
isLineVisible.value = true
|
||||
linePosition.value = pos;
|
||||
isLineVisible.value = true;
|
||||
} else {
|
||||
isLineVisible.value = false
|
||||
}
|
||||
}, 100)
|
||||
isLineVisible.value = false;
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
|
||||
const Category = defineComponent({
|
||||
name: 'Category',
|
||||
@@ -251,74 +286,78 @@ const Category = defineComponent({
|
||||
},
|
||||
setup(props) {
|
||||
interface ItemType {
|
||||
sub: string
|
||||
path: string
|
||||
isActive: boolean
|
||||
isNew: boolean
|
||||
isUpdated: boolean
|
||||
sub: string;
|
||||
path: string;
|
||||
isActive: boolean;
|
||||
isNew: boolean;
|
||||
isUpdated: boolean;
|
||||
}
|
||||
|
||||
const items = computed(() =>
|
||||
props.category.subcategories.map((sub: string): ItemType => {
|
||||
const path = `/${slug(props.category.name)}/${slug(sub)}`
|
||||
const activePath = props.pendingActivePath || props.location.path
|
||||
const path = `/${slug(props.category.name)}/${slug(sub)}`;
|
||||
const activePath = props.pendingActivePath || props.location.path;
|
||||
return {
|
||||
sub,
|
||||
path,
|
||||
isActive: activePath === path,
|
||||
isNew: (NEW as string[]).includes(sub),
|
||||
isUpdated: (UPDATED as string[]).includes(sub),
|
||||
}
|
||||
isUpdated: (UPDATED as string[]).includes(sub)
|
||||
};
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return () => h('div', { class: 'category' }, [
|
||||
return () =>
|
||||
h('div', { class: 'category' }, [
|
||||
h('p', { class: 'category-name' }, props.category.name),
|
||||
h('div', { class: 'category-items' },
|
||||
h(
|
||||
'div',
|
||||
{ class: 'category-items' },
|
||||
items.value.map(({ sub, path, isActive, isNew, isUpdated }: ItemType) => {
|
||||
return h('router-link', {
|
||||
return h(
|
||||
'router-link',
|
||||
{
|
||||
key: path,
|
||||
to: path,
|
||||
class: [
|
||||
'sidebar-item',
|
||||
{ 'active-sidebar-item': isActive },
|
||||
{ 'transitioning': props.isTransitioning }
|
||||
],
|
||||
class: ['sidebar-item', { 'active-sidebar-item': isActive }, { transitioning: props.isTransitioning }],
|
||||
onClick: (e: Event) => {
|
||||
e.preventDefault()
|
||||
props.handleTransitionNavigation(path)
|
||||
e.preventDefault();
|
||||
props.handleTransitionNavigation(path);
|
||||
},
|
||||
onMouseenter: (e: Event) => props.onItemMouseEnter(path, e),
|
||||
onMouseleave: props.onItemMouseLeave
|
||||
}, {
|
||||
default: () => [
|
||||
},
|
||||
{
|
||||
default: () =>
|
||||
[
|
||||
sub,
|
||||
isNew ? h('span', { class: 'new-tag' }, 'New') : null,
|
||||
isUpdated ? h('span', { class: 'updated-tag' }, 'Updated') : null
|
||||
].filter(Boolean)
|
||||
})
|
||||
}
|
||||
);
|
||||
})
|
||||
)
|
||||
])
|
||||
]);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
watch(() => route.path, updateActiveLine)
|
||||
watch(pendingActivePath, updateActiveLine)
|
||||
watch(() => route.path, updateActiveLine);
|
||||
watch(pendingActivePath, updateActiveLine);
|
||||
|
||||
onMounted(() => {
|
||||
updateActiveLine()
|
||||
updateActiveLine();
|
||||
if (sidebarContainerRef.value) {
|
||||
sidebarContainerRef.value.addEventListener('scroll', handleScroll)
|
||||
handleScroll()
|
||||
sidebarContainerRef.value.addEventListener('scroll', handleScroll);
|
||||
handleScroll();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (hoverTimeoutRef) clearTimeout(hoverTimeoutRef)
|
||||
if (hoverDelayTimeoutRef) clearTimeout(hoverDelayTimeoutRef)
|
||||
if (hoverTimeoutRef) clearTimeout(hoverTimeoutRef);
|
||||
if (hoverDelayTimeoutRef) clearTimeout(hoverDelayTimeoutRef);
|
||||
if (sidebarContainerRef.value) {
|
||||
sidebarContainerRef.value.removeEventListener('scroll', handleScroll)
|
||||
sidebarContainerRef.value.removeEventListener('scroll', handleScroll);
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
@@ -1,18 +1,18 @@
|
||||
import { ref } from 'vue'
|
||||
import { ref } from 'vue';
|
||||
|
||||
/**
|
||||
* Composable for force re-rendering components
|
||||
* Useful for demo components that need to restart animations or reset state
|
||||
*/
|
||||
export function useForceRerender() {
|
||||
const rerenderKey = ref(0)
|
||||
const rerenderKey = ref(0);
|
||||
|
||||
const forceRerender = () => {
|
||||
rerenderKey.value++
|
||||
}
|
||||
rerenderKey.value++;
|
||||
};
|
||||
|
||||
return {
|
||||
rerenderKey,
|
||||
forceRerender
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,48 +1,51 @@
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { getStarsCount } from '@/utils/utils'
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { getStarsCount } from '@/utils/utils';
|
||||
|
||||
const CACHE_KEY = 'github_stars_cache'
|
||||
const CACHE_DURATION = 24 * 60 * 60 * 1000
|
||||
const CACHE_KEY = 'github_stars_cache';
|
||||
const CACHE_DURATION = 24 * 60 * 60 * 1000;
|
||||
|
||||
export function useStars() {
|
||||
const stars = ref<number>(0)
|
||||
const stars = ref<number>(0);
|
||||
|
||||
const fetchStars = async () => {
|
||||
try {
|
||||
const cachedData = localStorage.getItem(CACHE_KEY)
|
||||
const cachedData = localStorage.getItem(CACHE_KEY);
|
||||
|
||||
if (cachedData) {
|
||||
const { count, timestamp } = JSON.parse(cachedData)
|
||||
const now = Date.now()
|
||||
const { count, timestamp } = JSON.parse(cachedData);
|
||||
const now = Date.now();
|
||||
|
||||
if (now - timestamp < CACHE_DURATION) {
|
||||
stars.value = count
|
||||
return
|
||||
stars.value = count;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const count = await getStarsCount()
|
||||
const count = await getStarsCount();
|
||||
|
||||
localStorage.setItem(CACHE_KEY, JSON.stringify({
|
||||
localStorage.setItem(
|
||||
CACHE_KEY,
|
||||
JSON.stringify({
|
||||
count,
|
||||
timestamp: Date.now()
|
||||
}))
|
||||
})
|
||||
);
|
||||
|
||||
stars.value = count
|
||||
stars.value = count;
|
||||
} catch (error) {
|
||||
console.error('Error fetching stars:', error)
|
||||
console.error('Error fetching stars:', error);
|
||||
|
||||
const cachedData = localStorage.getItem(CACHE_KEY)
|
||||
const cachedData = localStorage.getItem(CACHE_KEY);
|
||||
if (cachedData) {
|
||||
const { count } = JSON.parse(cachedData)
|
||||
stars.value = count
|
||||
}
|
||||
const { count } = JSON.parse(cachedData);
|
||||
stars.value = count;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchStars()
|
||||
})
|
||||
fetchStars();
|
||||
});
|
||||
|
||||
return stars
|
||||
return stars;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export const CATEGORIES = [
|
||||
'Text Cursor',
|
||||
'Decrypted Text',
|
||||
'True Focus',
|
||||
'Scroll Float',
|
||||
'Scroll Float'
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -34,7 +34,7 @@ export const CATEGORIES = [
|
||||
'Count Up',
|
||||
'Click Spark',
|
||||
'Magnet',
|
||||
'Cubes',
|
||||
'Cubes'
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -55,8 +55,8 @@ export const CATEGORIES = [
|
||||
'Glass Icons',
|
||||
'Decay Card',
|
||||
'Flowing Menu',
|
||||
'Elastic Slider',
|
||||
],
|
||||
'Elastic Slider'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Backgrounds',
|
||||
@@ -73,6 +73,6 @@ export const CATEGORIES = [
|
||||
'Iridescence',
|
||||
'Threads',
|
||||
'Grid Motion'
|
||||
],
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,69 +1,69 @@
|
||||
const animations = {
|
||||
'fade-content': () => import("../demo/Animations/FadeContentDemo.vue"),
|
||||
'animated-content': () => import("../demo/Animations/AnimatedContentDemo.vue"),
|
||||
'pixel-transition': () => import("../demo/Animations/PixelTransitionDemo.vue"),
|
||||
'glare-hover': () => import("../demo/Animations/GlareHoverDemo.vue"),
|
||||
'magnet-lines': () => import("../demo/Animations/MagnetLinesDemo.vue"),
|
||||
'click-spark': () => import("../demo/Animations/ClickSparkDemo.vue"),
|
||||
'magnet': () => import("../demo/Animations/MagnetDemo.vue"),
|
||||
'cubes': () => import("../demo/Animations/CubesDemo.vue"),
|
||||
'count-up': () => import("../demo/Animations/CountUpDemo.vue"),
|
||||
'fade-content': () => import('../demo/Animations/FadeContentDemo.vue'),
|
||||
'animated-content': () => import('../demo/Animations/AnimatedContentDemo.vue'),
|
||||
'pixel-transition': () => import('../demo/Animations/PixelTransitionDemo.vue'),
|
||||
'glare-hover': () => import('../demo/Animations/GlareHoverDemo.vue'),
|
||||
'magnet-lines': () => import('../demo/Animations/MagnetLinesDemo.vue'),
|
||||
'click-spark': () => import('../demo/Animations/ClickSparkDemo.vue'),
|
||||
magnet: () => import('../demo/Animations/MagnetDemo.vue'),
|
||||
cubes: () => import('../demo/Animations/CubesDemo.vue'),
|
||||
'count-up': () => import('../demo/Animations/CountUpDemo.vue')
|
||||
};
|
||||
|
||||
const textAnimations = {
|
||||
'split-text': () => import("../demo/TextAnimations/SplitTextDemo.vue"),
|
||||
'blur-text': () => import("../demo/TextAnimations/BlurTextDemo.vue"),
|
||||
'circular-text': () => import("../demo/TextAnimations/CircularTextDemo.vue"),
|
||||
'shiny-text': () => import("../demo/TextAnimations/ShinyTextDemo.vue"),
|
||||
'text-pressure': () => import("../demo/TextAnimations/TextPressureDemo.vue"),
|
||||
'curved-loop': () => import("../demo/TextAnimations/CurvedLoopDemo.vue"),
|
||||
'fuzzy-text': () => import("../demo/TextAnimations/FuzzyTextDemo.vue"),
|
||||
'gradient-text': () => import("../demo/TextAnimations/GradientTextDemo.vue"),
|
||||
'text-trail': () => import("../demo/TextAnimations/TextTrailDemo.vue"),
|
||||
'falling-text': () => import("../demo/TextAnimations/FallingTextDemo.vue"),
|
||||
'text-cursor': () => import("../demo/TextAnimations/TextCursorDemo.vue"),
|
||||
'decrypted-text': () => import("../demo/TextAnimations/DecryptedTextDemo.vue"),
|
||||
'true-focus': () => import("../demo/TextAnimations/TrueFocusDemo.vue"),
|
||||
'scroll-float': () => import("../demo/TextAnimations/ScrollFloatDemo.vue"),
|
||||
'split-text': () => import('../demo/TextAnimations/SplitTextDemo.vue'),
|
||||
'blur-text': () => import('../demo/TextAnimations/BlurTextDemo.vue'),
|
||||
'circular-text': () => import('../demo/TextAnimations/CircularTextDemo.vue'),
|
||||
'shiny-text': () => import('../demo/TextAnimations/ShinyTextDemo.vue'),
|
||||
'text-pressure': () => import('../demo/TextAnimations/TextPressureDemo.vue'),
|
||||
'curved-loop': () => import('../demo/TextAnimations/CurvedLoopDemo.vue'),
|
||||
'fuzzy-text': () => import('../demo/TextAnimations/FuzzyTextDemo.vue'),
|
||||
'gradient-text': () => import('../demo/TextAnimations/GradientTextDemo.vue'),
|
||||
'text-trail': () => import('../demo/TextAnimations/TextTrailDemo.vue'),
|
||||
'falling-text': () => import('../demo/TextAnimations/FallingTextDemo.vue'),
|
||||
'text-cursor': () => import('../demo/TextAnimations/TextCursorDemo.vue'),
|
||||
'decrypted-text': () => import('../demo/TextAnimations/DecryptedTextDemo.vue'),
|
||||
'true-focus': () => import('../demo/TextAnimations/TrueFocusDemo.vue'),
|
||||
'scroll-float': () => import('../demo/TextAnimations/ScrollFloatDemo.vue')
|
||||
};
|
||||
|
||||
const components = {
|
||||
'masonry': () => import("../demo/Components/MasonryDemo.vue"),
|
||||
'profile-card': () => import("../demo/Components/ProfileCardDemo.vue"),
|
||||
'dock': () => import("../demo/Components/DockDemo.vue"),
|
||||
'gooey-nav': () => import("../demo/Components/GooeyNavDemo.vue"),
|
||||
'pixel-card': () => import("../demo/Components/PixelCardDemo.vue"),
|
||||
'carousel': () => import("../demo/Components/CarouselDemo.vue"),
|
||||
'spotlight-card': () => import("../demo/Components/SpotlightCardDemo.vue"),
|
||||
'circular-gallery': () => import("../demo/Components/CircularGalleryDemo.vue"),
|
||||
'flying-posters': () => import("../demo/Components/FlyingPostersDemo.vue"),
|
||||
'card-swap': () => import("../demo/Components/CardSwapDemo.vue"),
|
||||
'infinite-scroll': () => import("../demo/Components/InfiniteScrollDemo.vue"),
|
||||
'glass-icons': () => import("../demo/Components/GlassIconsDemo.vue"),
|
||||
'decay-card': () => import("../demo/Components/DecayCardDemo.vue"),
|
||||
'flowing-menu': () => import("../demo/Components/FlowingMenuDemo.vue"),
|
||||
'elastic-slider': () => import("../demo/Components/ElasticSliderDemo.vue"),
|
||||
'tilted-card': () => import("../demo/Components/TiltedCardDemo.vue"),
|
||||
masonry: () => import('../demo/Components/MasonryDemo.vue'),
|
||||
'profile-card': () => import('../demo/Components/ProfileCardDemo.vue'),
|
||||
dock: () => import('../demo/Components/DockDemo.vue'),
|
||||
'gooey-nav': () => import('../demo/Components/GooeyNavDemo.vue'),
|
||||
'pixel-card': () => import('../demo/Components/PixelCardDemo.vue'),
|
||||
carousel: () => import('../demo/Components/CarouselDemo.vue'),
|
||||
'spotlight-card': () => import('../demo/Components/SpotlightCardDemo.vue'),
|
||||
'circular-gallery': () => import('../demo/Components/CircularGalleryDemo.vue'),
|
||||
'flying-posters': () => import('../demo/Components/FlyingPostersDemo.vue'),
|
||||
'card-swap': () => import('../demo/Components/CardSwapDemo.vue'),
|
||||
'infinite-scroll': () => import('../demo/Components/InfiniteScrollDemo.vue'),
|
||||
'glass-icons': () => import('../demo/Components/GlassIconsDemo.vue'),
|
||||
'decay-card': () => import('../demo/Components/DecayCardDemo.vue'),
|
||||
'flowing-menu': () => import('../demo/Components/FlowingMenuDemo.vue'),
|
||||
'elastic-slider': () => import('../demo/Components/ElasticSliderDemo.vue'),
|
||||
'tilted-card': () => import('../demo/Components/TiltedCardDemo.vue')
|
||||
};
|
||||
|
||||
const backgrounds = {
|
||||
'dot-grid': () => import("../demo/Backgrounds/DotGridDemo.vue"),
|
||||
'silk': () => import("../demo/Backgrounds/SilkDemo.vue"),
|
||||
'lightning': () => import("../demo/Backgrounds/LightningDemo.vue"),
|
||||
'letter-glitch': () => import("../demo/Backgrounds/LetterGlitchDemo.vue"),
|
||||
'particles': () => import("../demo/Backgrounds/ParticlesDemo.vue"),
|
||||
'waves': () => import("../demo/Backgrounds/WavesDemo.vue"),
|
||||
'squares': () => import("../demo/Backgrounds/SquaresDemo.vue"),
|
||||
'iridescence': () => import("../demo/Backgrounds/IridescenceDemo.vue"),
|
||||
'threads': () => import("../demo/Backgrounds/ThreadsDemo.vue"),
|
||||
'aurora': () => import("../demo/Backgrounds/AuroraDemo.vue"),
|
||||
'beams': () => import("../demo/Backgrounds/BeamsDemo.vue"),
|
||||
'grid-motion': () => import("../demo/Backgrounds/GridMotionDemo.vue"),
|
||||
'dot-grid': () => import('../demo/Backgrounds/DotGridDemo.vue'),
|
||||
silk: () => import('../demo/Backgrounds/SilkDemo.vue'),
|
||||
lightning: () => import('../demo/Backgrounds/LightningDemo.vue'),
|
||||
'letter-glitch': () => import('../demo/Backgrounds/LetterGlitchDemo.vue'),
|
||||
particles: () => import('../demo/Backgrounds/ParticlesDemo.vue'),
|
||||
waves: () => import('../demo/Backgrounds/WavesDemo.vue'),
|
||||
squares: () => import('../demo/Backgrounds/SquaresDemo.vue'),
|
||||
iridescence: () => import('../demo/Backgrounds/IridescenceDemo.vue'),
|
||||
threads: () => import('../demo/Backgrounds/ThreadsDemo.vue'),
|
||||
aurora: () => import('../demo/Backgrounds/AuroraDemo.vue'),
|
||||
beams: () => import('../demo/Backgrounds/BeamsDemo.vue'),
|
||||
'grid-motion': () => import('../demo/Backgrounds/GridMotionDemo.vue')
|
||||
};
|
||||
|
||||
export const componentMap = {
|
||||
...animations,
|
||||
...textAnimations,
|
||||
...components,
|
||||
...backgrounds,
|
||||
...backgrounds
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@/content/Animations/AnimatedContent/AnimatedContent.vue?raw'
|
||||
import type { CodeObject } from '@/types/code'
|
||||
import code from '@/content/Animations/AnimatedContent/AnimatedContent.vue?raw';
|
||||
import type { CodeObject } from '@/types/code';
|
||||
|
||||
export const animatedContent: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/AnimatedContent`,
|
||||
@@ -32,4 +32,4 @@ export const animatedContent: CodeObject = {
|
||||
};
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Animations/ClickSpark/ClickSpark.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Animations/ClickSpark/ClickSpark.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const clickSpark: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/ClickSpark`,
|
||||
@@ -44,4 +44,4 @@ import ClickSpark from '@/content/Animations/ClickSpark/ClickSpark.vue'
|
||||
}
|
||||
</style>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@/content/Animations/CountUp/CountUp.vue?raw'
|
||||
import type { CodeObject } from '@/types/code'
|
||||
import code from '@/content/Animations/CountUp/CountUp.vue?raw';
|
||||
import type { CodeObject } from '@/types/code';
|
||||
|
||||
export const countup: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/CountUp`,
|
||||
@@ -30,4 +30,4 @@ export const countup: CodeObject = {
|
||||
};
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Animations/Cubes/Cubes.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Animations/Cubes/Cubes.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const cubes: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/Cubes`,
|
||||
@@ -29,4 +29,4 @@ export const cubes: CodeObject = {
|
||||
import Cubes from "./Cubes.vue";
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Animations/FadeContent/FadeContent.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Animations/FadeContent/FadeContent.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const fadeContent: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/FadeContent`,
|
||||
@@ -24,4 +24,4 @@ export const fadeContent: CodeObject = {
|
||||
import FadeContent from "./FadeContent.vue";
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@/content/Animations/GlareHover/GlareHover.vue?raw'
|
||||
import type { CodeObject } from '@/types/code'
|
||||
import code from '@/content/Animations/GlareHover/GlareHover.vue?raw';
|
||||
import type { CodeObject } from '@/types/code';
|
||||
|
||||
export const glareHover: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/GlareHover`,
|
||||
@@ -26,4 +26,4 @@ export const glareHover: CodeObject = {
|
||||
import GlareHover from "./GlareHover.vue";
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Animations/Magnet/Magnet.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Animations/Magnet/Magnet.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const magnet: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/Magnet`,
|
||||
@@ -45,4 +45,4 @@ import Magnet from '@/content/Animations/Magnet/Magnet.vue'
|
||||
}
|
||||
</style>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@/content/Animations/MagnetLines/MagnetLines.vue?raw'
|
||||
import type { CodeObject } from '@/types/code'
|
||||
import code from '@/content/Animations/MagnetLines/MagnetLines.vue?raw';
|
||||
import type { CodeObject } from '@/types/code';
|
||||
|
||||
export const magnetLines: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/MagnetLines`,
|
||||
@@ -19,4 +19,4 @@ export const magnetLines: CodeObject = {
|
||||
import MagnetLines from "./MagnetLines.vue";
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@/content/Animations/PixelTransition/PixelTransition.vue?raw'
|
||||
import type { CodeObject } from '@/types/code'
|
||||
import code from '@/content/Animations/PixelTransition/PixelTransition.vue?raw';
|
||||
import type { CodeObject } from '@/types/code';
|
||||
|
||||
export const pixelTransition: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/PixelTransition`,
|
||||
@@ -26,4 +26,4 @@ export const pixelTransition: CodeObject = {
|
||||
import PixelTransition from './PixelTransition.vue';
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Backgrounds/Aurora/Aurora.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Backgrounds/Aurora/Aurora.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const aurora: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/Aurora`,
|
||||
@@ -30,4 +30,4 @@ export const aurora: CodeObject = {
|
||||
}
|
||||
</style>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Backgrounds/Beams/Beams.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Backgrounds/Beams/Beams.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const beams: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/Beams`,
|
||||
@@ -33,4 +33,4 @@ export const beams: CodeObject = {
|
||||
}
|
||||
</style>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Backgrounds/DotGrid/DotGrid.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Backgrounds/DotGrid/DotGrid.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const dotGrid: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/DotGrid`,
|
||||
@@ -36,4 +36,4 @@ export const dotGrid: CodeObject = {
|
||||
}
|
||||
</style>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Backgrounds/Iridescence/Iridescence.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Backgrounds/Iridescence/Iridescence.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const iridescence: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/Iridescence`,
|
||||
@@ -19,4 +19,4 @@ export const iridescence: CodeObject = {
|
||||
import Iridescence from "./Iridescence.vue";
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Backgrounds/LetterGlitch/LetterGlitch.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Backgrounds/LetterGlitch/LetterGlitch.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const letterGlitch: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/LetterGlitch`,
|
||||
@@ -29,4 +29,4 @@ export const letterGlitch: CodeObject = {
|
||||
}
|
||||
</style>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Backgrounds/Lightning/Lightning.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Backgrounds/Lightning/Lightning.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const lightning: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/Lightning`,
|
||||
@@ -30,4 +30,4 @@ export const lightning: CodeObject = {
|
||||
}
|
||||
</style>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Backgrounds/Particles/Particles.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Backgrounds/Particles/Particles.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const particles: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/Particles`,
|
||||
@@ -36,4 +36,4 @@ export const particles: CodeObject = {
|
||||
}
|
||||
</style>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Backgrounds/Silk/Silk.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Backgrounds/Silk/Silk.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const silk: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/Silk`,
|
||||
@@ -30,4 +30,4 @@ export const silk: CodeObject = {
|
||||
}
|
||||
</style>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Backgrounds/Squares/Squares.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Backgrounds/Squares/Squares.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const squares: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/Squares`,
|
||||
@@ -19,4 +19,4 @@ export const squares: CodeObject = {
|
||||
import Squares from "./Squares.vue";
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Backgrounds/Threads/Threads.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Backgrounds/Threads/Threads.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const threads: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/Threads`,
|
||||
@@ -19,4 +19,4 @@ export const threads: CodeObject = {
|
||||
import Threads from "./Threads.vue";
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Backgrounds/Waves/Waves.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Backgrounds/Waves/Waves.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const waves: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/Waves`,
|
||||
@@ -35,4 +35,4 @@ export const waves: CodeObject = {
|
||||
}
|
||||
</style>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Components/CardSwap/CardSwap.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Components/CardSwap/CardSwap.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const cardSwap: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/CardSwap`,
|
||||
@@ -51,4 +51,4 @@ export const cardSwap: CodeObject = {
|
||||
};
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Components/Carousel/Carousel.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Components/Carousel/Carousel.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const carousel: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/Carousel`,
|
||||
@@ -37,4 +37,4 @@ export const carousel: CodeObject = {
|
||||
];
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Components/CircularGallery/CircularGallery.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Components/CircularGallery/CircularGallery.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const circularGallery: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/CircularGallery`,
|
||||
@@ -24,4 +24,4 @@ export const circularGallery: CodeObject = {
|
||||
import CircularGallery from "./CircularGallery.vue";
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Components/DecayCard/DecayCard.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Components/DecayCard/DecayCard.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const decayCard: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/DecayCard`,
|
||||
@@ -20,4 +20,4 @@ export const decayCard: CodeObject = {
|
||||
import DecayCard from "./DecayCard.vue";
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Components/Dock/Dock.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Components/Dock/Dock.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const dock: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/Dock`,
|
||||
@@ -44,4 +44,4 @@ export const dock: CodeObject = {
|
||||
];
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Components/ElasticSlider/ElasticSlider.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Components/ElasticSlider/ElasticSlider.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const elasticSlider: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/ElasticSlider`,
|
||||
@@ -27,4 +27,4 @@ export const elasticSlider: CodeObject = {
|
||||
import ElasticSlider from "./ElasticSlider.vue";
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Components/FlowingMenu/FlowingMenu.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Components/FlowingMenu/FlowingMenu.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const flowingMenu: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/FlowingMenu`,
|
||||
@@ -19,4 +19,4 @@ export const flowingMenu: CodeObject = {
|
||||
];
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Components/FlyingPosters/FlyingPosters.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Components/FlyingPosters/FlyingPosters.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const flyingPosters: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/FlyingPosters`,
|
||||
@@ -34,4 +34,4 @@ export const flyingPosters: CodeObject = {
|
||||
];
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Components/GlassIcons/GlassIcons.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Components/GlassIcons/GlassIcons.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const glassIcons: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/GlassIcons`,
|
||||
@@ -20,4 +20,4 @@ export const glassIcons: CodeObject = {
|
||||
];
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Components/GooeyNav/GooeyNav.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Components/GooeyNav/GooeyNav.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const gooeyNav: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/GooeyNav`,
|
||||
@@ -37,4 +37,4 @@ export const gooeyNav: CodeObject = {
|
||||
}
|
||||
</style>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Components/InfiniteScroll/InfiniteScroll.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Components/InfiniteScroll/InfiniteScroll.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const infiniteScroll: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/InfiniteScroll`,
|
||||
@@ -31,4 +31,4 @@ export const infiniteScroll: CodeObject = {
|
||||
];
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Components/Masonry/Masonry.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Components/Masonry/Masonry.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const masonry: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/Masonry`,
|
||||
@@ -29,4 +29,4 @@ const items = ref([
|
||||
])
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Components/PixelCard/PixelCard.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Components/PixelCard/PixelCard.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const pixelCard: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/PixelCard`,
|
||||
@@ -18,4 +18,4 @@ export const pixelCard: CodeObject = {
|
||||
import PixelCard from "./PixelCard.vue";
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Components/ProfileCard/ProfileCard.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Components/ProfileCard/ProfileCard.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const profileCard: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/ProfileCard`,
|
||||
@@ -28,4 +28,4 @@ export const profileCard: CodeObject = {
|
||||
};
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Components/SpotlightCard/SpotlightCard.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Components/SpotlightCard/SpotlightCard.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const spotlightCard: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/SpotlightCard`,
|
||||
@@ -16,4 +16,4 @@ export const spotlightCard: CodeObject = {
|
||||
import SpotlightCard from "./SpotlightCard.vue";
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/Components/TiltedCard/TiltedCard.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/Components/TiltedCard/TiltedCard.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const tiltedCard: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/TiltedCard`,
|
||||
@@ -31,4 +31,4 @@ export const tiltedCard: CodeObject = {
|
||||
import TiltedCard from "./TiltedCard.vue";
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/TextAnimations/BlurText/BlurText.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/TextAnimations/BlurText/BlurText.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const blurText: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/BlurText`,
|
||||
@@ -26,4 +26,4 @@ export const blurText: CodeObject = {
|
||||
};
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/TextAnimations/CircularText/CircularText.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/TextAnimations/CircularText/CircularText.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const circularText: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/CircularText`,
|
||||
@@ -17,4 +17,4 @@ export const circularText: CodeObject = {
|
||||
import CircularText from "./CircularText.vue";
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/TextAnimations/CurvedLoop/CurvedLoop.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/TextAnimations/CurvedLoop/CurvedLoop.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const curvedLoop: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/CurvedLoop`,
|
||||
@@ -17,4 +17,4 @@ export const curvedLoop: CodeObject = {
|
||||
import CurvedLoop from "./CurvedLoop.vue";
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@/content/TextAnimations/DecryptedText/DecryptedText.vue?raw'
|
||||
import type { CodeObject } from '@/types/code'
|
||||
import code from '@/content/TextAnimations/DecryptedText/DecryptedText.vue?raw';
|
||||
import type { CodeObject } from '@/types/code';
|
||||
|
||||
export const decryptedText: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/DecryptedText`,
|
||||
@@ -21,4 +21,4 @@ export const decryptedText: CodeObject = {
|
||||
import DecryptedText from "./DecryptedText.vue";
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@/content/TextAnimations/FallingText/FallingText.vue?raw'
|
||||
import type { CodeObject } from '@/types/code'
|
||||
import code from '@/content/TextAnimations/FallingText/FallingText.vue?raw';
|
||||
import type { CodeObject } from '@/types/code';
|
||||
|
||||
export const fallingText: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/FallingText`,
|
||||
@@ -19,4 +19,4 @@ export const fallingText: CodeObject = {
|
||||
import FallingText from "./FallingText.vue";
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/TextAnimations/FuzzyText/FuzzyText.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/TextAnimations/FuzzyText/FuzzyText.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const fuzzyText: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/FuzzyText`,
|
||||
@@ -19,4 +19,4 @@ export const fuzzyText: CodeObject = {
|
||||
import FuzzyText from "./FuzzyText.vue";
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/TextAnimations/GradientText/GradientText.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/TextAnimations/GradientText/GradientText.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const gradientText: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/GradientText`,
|
||||
@@ -17,4 +17,4 @@ export const gradientText: CodeObject = {
|
||||
import GradientText from "./GradientText.vue";
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/TextAnimations/ScrollFloat/ScrollFloat.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/TextAnimations/ScrollFloat/ScrollFloat.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const scrollFloatCode: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/ScrollFloat`,
|
||||
@@ -22,4 +22,4 @@ export const scrollFloatCode: CodeObject = {
|
||||
import ScrollFloat from "./ScrollFloat.vue";
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/TextAnimations/ShinyText/ShinyText.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/TextAnimations/ShinyText/ShinyText.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const shinyText: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/ShinyText`,
|
||||
@@ -16,4 +16,4 @@ export const shinyText: CodeObject = {
|
||||
import ShinyText from "./ShinyText.vue";
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Fun fact: this is the first component ever made for Vue Bits!
|
||||
import code from '@content/TextAnimations/SplitText/SplitText.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/TextAnimations/SplitText/SplitText.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const splitText: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/SplitText`,
|
||||
@@ -30,4 +30,4 @@ export const splitText: CodeObject = {
|
||||
};
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@/content/TextAnimations/TextCursor/TextCursor.vue?raw'
|
||||
import type { CodeObject } from '@/types/code'
|
||||
import code from '@/content/TextAnimations/TextCursor/TextCursor.vue?raw';
|
||||
import type { CodeObject } from '@/types/code';
|
||||
|
||||
export const textCursor: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/TextCursor`,
|
||||
@@ -21,4 +21,4 @@ export const textCursor: CodeObject = {
|
||||
import TextCursor from "./TextCursor.vue";
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@content/TextAnimations/TextPressure/TextPressure.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
import code from '@content/TextAnimations/TextPressure/TextPressure.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const textPressure: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/TextPressure`,
|
||||
@@ -22,4 +22,4 @@ export const textPressure: CodeObject = {
|
||||
import TextPressure from "./TextPressure.vue";
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from '@/content/TextAnimations/TextTrail/TextTrail.vue?raw'
|
||||
import type { CodeObject } from '@/types/code'
|
||||
import code from '@/content/TextAnimations/TextTrail/TextTrail.vue?raw';
|
||||
import type { CodeObject } from '@/types/code';
|
||||
|
||||
export const textTrail: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/TextTrail`,
|
||||
@@ -20,4 +20,4 @@ export const textTrail: CodeObject = {
|
||||
import TextTrail from "./TextTrail.vue";
|
||||
</script>`,
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import code from "@/content/TextAnimations/TrueFocus/TrueFocus.vue?raw";
|
||||
import type { CodeObject } from "../../../types/code";
|
||||
import code from '@/content/TextAnimations/TrueFocus/TrueFocus.vue?raw';
|
||||
import type { CodeObject } from '../../../types/code';
|
||||
|
||||
export const trueFocus: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/TrueFocus`,
|
||||
@@ -18,5 +18,5 @@ export const trueFocus: CodeObject = {
|
||||
<script setup lang="ts">
|
||||
import TrueFocus from "./TrueFocus.vue";
|
||||
</script>`,
|
||||
code,
|
||||
code
|
||||
};
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { gsap } from 'gsap'
|
||||
import { ScrollTrigger } from 'gsap/ScrollTrigger'
|
||||
import { ref, onMounted, onUnmounted, watch } from 'vue';
|
||||
import { gsap } from 'gsap';
|
||||
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||
|
||||
gsap.registerPlugin(ScrollTrigger)
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
interface AnimatedContentProps {
|
||||
distance?: number
|
||||
direction?: 'vertical' | 'horizontal'
|
||||
reverse?: boolean
|
||||
duration?: number
|
||||
ease?: string | ((progress: number) => number)
|
||||
initialOpacity?: number
|
||||
animateOpacity?: boolean
|
||||
scale?: number
|
||||
threshold?: number
|
||||
delay?: number
|
||||
className?: string
|
||||
distance?: number;
|
||||
direction?: 'vertical' | 'horizontal';
|
||||
reverse?: boolean;
|
||||
duration?: number;
|
||||
ease?: string | ((progress: number) => number);
|
||||
initialOpacity?: number;
|
||||
animateOpacity?: boolean;
|
||||
scale?: number;
|
||||
threshold?: number;
|
||||
delay?: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<AnimatedContentProps>(), {
|
||||
@@ -31,27 +31,27 @@ const props = withDefaults(defineProps<AnimatedContentProps>(), {
|
||||
threshold: 0.1,
|
||||
delay: 0,
|
||||
className: ''
|
||||
})
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
complete: []
|
||||
}>()
|
||||
complete: [];
|
||||
}>();
|
||||
|
||||
const containerRef = ref<HTMLDivElement>()
|
||||
const containerRef = ref<HTMLDivElement>();
|
||||
|
||||
onMounted(() => {
|
||||
const el = containerRef.value
|
||||
if (!el) return
|
||||
const el = containerRef.value;
|
||||
if (!el) return;
|
||||
|
||||
const axis = props.direction === 'horizontal' ? 'x' : 'y'
|
||||
const offset = props.reverse ? -props.distance : props.distance
|
||||
const startPct = (1 - props.threshold) * 100
|
||||
const axis = props.direction === 'horizontal' ? 'x' : 'y';
|
||||
const offset = props.reverse ? -props.distance : props.distance;
|
||||
const startPct = (1 - props.threshold) * 100;
|
||||
|
||||
gsap.set(el, {
|
||||
[axis]: offset,
|
||||
scale: props.scale,
|
||||
opacity: props.animateOpacity ? props.initialOpacity : 1,
|
||||
})
|
||||
opacity: props.animateOpacity ? props.initialOpacity : 1
|
||||
});
|
||||
|
||||
gsap.to(el, {
|
||||
[axis]: 0,
|
||||
@@ -65,10 +65,10 @@ onMounted(() => {
|
||||
trigger: el,
|
||||
start: `top ${startPct}%`,
|
||||
toggleActions: 'play none none none',
|
||||
once: true,
|
||||
},
|
||||
})
|
||||
})
|
||||
once: true
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
watch(
|
||||
() => [
|
||||
@@ -81,24 +81,24 @@ watch(
|
||||
props.animateOpacity,
|
||||
props.scale,
|
||||
props.threshold,
|
||||
props.delay,
|
||||
props.delay
|
||||
],
|
||||
() => {
|
||||
const el = containerRef.value
|
||||
if (!el) return
|
||||
const el = containerRef.value;
|
||||
if (!el) return;
|
||||
|
||||
ScrollTrigger.getAll().forEach((t) => t.kill())
|
||||
gsap.killTweensOf(el)
|
||||
ScrollTrigger.getAll().forEach(t => t.kill());
|
||||
gsap.killTweensOf(el);
|
||||
|
||||
const axis = props.direction === 'horizontal' ? 'x' : 'y'
|
||||
const offset = props.reverse ? -props.distance : props.distance
|
||||
const startPct = (1 - props.threshold) * 100
|
||||
const axis = props.direction === 'horizontal' ? 'x' : 'y';
|
||||
const offset = props.reverse ? -props.distance : props.distance;
|
||||
const startPct = (1 - props.threshold) * 100;
|
||||
|
||||
gsap.set(el, {
|
||||
[axis]: offset,
|
||||
scale: props.scale,
|
||||
opacity: props.animateOpacity ? props.initialOpacity : 1,
|
||||
})
|
||||
opacity: props.animateOpacity ? props.initialOpacity : 1
|
||||
});
|
||||
|
||||
gsap.to(el, {
|
||||
[axis]: 0,
|
||||
@@ -112,27 +112,24 @@ watch(
|
||||
trigger: el,
|
||||
start: `top ${startPct}%`,
|
||||
toggleActions: 'play none none none',
|
||||
once: true,
|
||||
},
|
||||
})
|
||||
once: true
|
||||
}
|
||||
});
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
);
|
||||
|
||||
onUnmounted(() => {
|
||||
const el = containerRef.value
|
||||
const el = containerRef.value;
|
||||
if (el) {
|
||||
ScrollTrigger.getAll().forEach((t) => t.kill())
|
||||
gsap.killTweensOf(el)
|
||||
ScrollTrigger.getAll().forEach(t => t.kill());
|
||||
gsap.killTweensOf(el);
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
ref="containerRef"
|
||||
:class="`animated-content ${props.className}`"
|
||||
>
|
||||
<div ref="containerRef" :class="`animated-content ${props.className}`">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,35 +1,29 @@
|
||||
<template>
|
||||
<div
|
||||
ref="containerRef"
|
||||
class="relative w-full h-full"
|
||||
@click="handleClick"
|
||||
>
|
||||
<canvas
|
||||
ref="canvasRef"
|
||||
class="absolute inset-0 pointer-events-none"
|
||||
/>
|
||||
<div ref="containerRef" class="relative w-full h-full" @click="handleClick">
|
||||
<canvas ref="canvasRef" class="absolute inset-0 pointer-events-none" />
|
||||
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, computed, watch } from 'vue'
|
||||
import { ref, onMounted, onUnmounted, computed, watch } from 'vue';
|
||||
|
||||
interface Spark {
|
||||
x: number
|
||||
y: number
|
||||
angle: number
|
||||
startTime: number
|
||||
x: number;
|
||||
y: number;
|
||||
angle: number;
|
||||
startTime: number;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
sparkColor?: string
|
||||
sparkSize?: number
|
||||
sparkRadius?: number
|
||||
sparkCount?: number
|
||||
duration?: number
|
||||
easing?: "linear" | "ease-in" | "ease-out" | "ease-in-out"
|
||||
extraScale?: number
|
||||
sparkColor?: string;
|
||||
sparkSize?: number;
|
||||
sparkRadius?: number;
|
||||
sparkCount?: number;
|
||||
duration?: number;
|
||||
easing?: 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out';
|
||||
extraScale?: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -40,138 +34,139 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
duration: 400,
|
||||
easing: 'ease-out',
|
||||
extraScale: 1.0
|
||||
})
|
||||
});
|
||||
|
||||
const containerRef = ref<HTMLDivElement | null>(null)
|
||||
const canvasRef = ref<HTMLCanvasElement | null>(null)
|
||||
const sparks = ref<Spark[]>([])
|
||||
const startTimeRef = ref<number | null>(null)
|
||||
const animationId = ref<number | null>(null)
|
||||
const containerRef = ref<HTMLDivElement | null>(null);
|
||||
const canvasRef = ref<HTMLCanvasElement | null>(null);
|
||||
const sparks = ref<Spark[]>([]);
|
||||
const startTimeRef = ref<number | null>(null);
|
||||
const animationId = ref<number | null>(null);
|
||||
|
||||
const easeFunc = computed(() => {
|
||||
return (t: number) => {
|
||||
switch (props.easing) {
|
||||
case "linear":
|
||||
return t
|
||||
case "ease-in":
|
||||
return t * t
|
||||
case "ease-in-out":
|
||||
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t
|
||||
case 'linear':
|
||||
return t;
|
||||
case 'ease-in':
|
||||
return t * t;
|
||||
case 'ease-in-out':
|
||||
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
|
||||
default:
|
||||
return t * (2 - t)
|
||||
return t * (2 - t);
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
const canvas = canvasRef.value
|
||||
if (!canvas) return
|
||||
const rect = canvas.getBoundingClientRect()
|
||||
const x = e.clientX - rect.left
|
||||
const y = e.clientY - rect.top
|
||||
const canvas = canvasRef.value;
|
||||
if (!canvas) return;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const y = e.clientY - rect.top;
|
||||
|
||||
const now = performance.now()
|
||||
const now = performance.now();
|
||||
const newSparks: Spark[] = Array.from({ length: props.sparkCount }, (_, i) => ({
|
||||
x,
|
||||
y,
|
||||
angle: (2 * Math.PI * i) / props.sparkCount,
|
||||
startTime: now,
|
||||
}))
|
||||
startTime: now
|
||||
}));
|
||||
|
||||
sparks.value.push(...newSparks)
|
||||
}
|
||||
sparks.value.push(...newSparks);
|
||||
};
|
||||
|
||||
const draw = (timestamp: number) => {
|
||||
if (!startTimeRef.value) {
|
||||
startTimeRef.value = timestamp
|
||||
startTimeRef.value = timestamp;
|
||||
}
|
||||
|
||||
const canvas = canvasRef.value
|
||||
const ctx = canvas?.getContext('2d')
|
||||
if (!ctx || !canvas) return
|
||||
const canvas = canvasRef.value;
|
||||
const ctx = canvas?.getContext('2d');
|
||||
if (!ctx || !canvas) return;
|
||||
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
sparks.value = sparks.value.filter((spark: Spark) => {
|
||||
const elapsed = timestamp - spark.startTime
|
||||
const elapsed = timestamp - spark.startTime;
|
||||
if (elapsed >= props.duration) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
const progress = elapsed / props.duration
|
||||
const eased = easeFunc.value(progress)
|
||||
const progress = elapsed / props.duration;
|
||||
const eased = easeFunc.value(progress);
|
||||
|
||||
const distance = eased * props.sparkRadius * props.extraScale
|
||||
const lineLength = props.sparkSize * (1 - eased)
|
||||
const distance = eased * props.sparkRadius * props.extraScale;
|
||||
const lineLength = props.sparkSize * (1 - eased);
|
||||
|
||||
const x1 = spark.x + distance * Math.cos(spark.angle)
|
||||
const y1 = spark.y + distance * Math.sin(spark.angle)
|
||||
const x2 = spark.x + (distance + lineLength) * Math.cos(spark.angle)
|
||||
const y2 = spark.y + (distance + lineLength) * Math.sin(spark.angle)
|
||||
const x1 = spark.x + distance * Math.cos(spark.angle);
|
||||
const y1 = spark.y + distance * Math.sin(spark.angle);
|
||||
const x2 = spark.x + (distance + lineLength) * Math.cos(spark.angle);
|
||||
const y2 = spark.y + (distance + lineLength) * Math.sin(spark.angle);
|
||||
|
||||
ctx.strokeStyle = props.sparkColor
|
||||
ctx.lineWidth = 2
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(x1, y1)
|
||||
ctx.lineTo(x2, y2)
|
||||
ctx.stroke()
|
||||
ctx.strokeStyle = props.sparkColor;
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x1, y1);
|
||||
ctx.lineTo(x2, y2);
|
||||
ctx.stroke();
|
||||
|
||||
return true
|
||||
})
|
||||
return true;
|
||||
});
|
||||
|
||||
animationId.value = requestAnimationFrame(draw)
|
||||
}
|
||||
animationId.value = requestAnimationFrame(draw);
|
||||
};
|
||||
|
||||
const resizeCanvas = () => {
|
||||
const canvas = canvasRef.value
|
||||
if (!canvas) return
|
||||
const canvas = canvasRef.value;
|
||||
if (!canvas) return;
|
||||
|
||||
const parent = canvas.parentElement
|
||||
if (!parent) return
|
||||
const parent = canvas.parentElement;
|
||||
if (!parent) return;
|
||||
|
||||
const { width, height } = parent.getBoundingClientRect()
|
||||
const { width, height } = parent.getBoundingClientRect();
|
||||
if (canvas.width !== width || canvas.height !== height) {
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
}
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
}
|
||||
};
|
||||
|
||||
let resizeTimeout: number
|
||||
let resizeTimeout: number;
|
||||
|
||||
const handleResize = () => {
|
||||
clearTimeout(resizeTimeout)
|
||||
resizeTimeout = setTimeout(resizeCanvas, 100)
|
||||
}
|
||||
clearTimeout(resizeTimeout);
|
||||
resizeTimeout = setTimeout(resizeCanvas, 100);
|
||||
};
|
||||
|
||||
let resizeObserver: ResizeObserver | null = null
|
||||
let resizeObserver: ResizeObserver | null = null;
|
||||
|
||||
onMounted(() => {
|
||||
const canvas = canvasRef.value
|
||||
if (!canvas) return
|
||||
const canvas = canvasRef.value;
|
||||
if (!canvas) return;
|
||||
|
||||
const parent = canvas.parentElement
|
||||
if (!parent) return
|
||||
const parent = canvas.parentElement;
|
||||
if (!parent) return;
|
||||
|
||||
resizeObserver = new ResizeObserver(handleResize)
|
||||
resizeObserver.observe(parent)
|
||||
resizeObserver = new ResizeObserver(handleResize);
|
||||
resizeObserver.observe(parent);
|
||||
|
||||
resizeCanvas()
|
||||
resizeCanvas();
|
||||
|
||||
animationId.value = requestAnimationFrame(draw)
|
||||
})
|
||||
animationId.value = requestAnimationFrame(draw);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (resizeObserver) {
|
||||
resizeObserver.disconnect()
|
||||
resizeObserver.disconnect();
|
||||
}
|
||||
clearTimeout(resizeTimeout)
|
||||
clearTimeout(resizeTimeout);
|
||||
|
||||
if (animationId.value) {
|
||||
cancelAnimationFrame(animationId.value)
|
||||
cancelAnimationFrame(animationId.value);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
watch([
|
||||
watch(
|
||||
[
|
||||
() => props.sparkColor,
|
||||
() => props.sparkSize,
|
||||
() => props.sparkRadius,
|
||||
@@ -179,10 +174,12 @@ watch([
|
||||
() => props.duration,
|
||||
easeFunc,
|
||||
() => props.extraScale
|
||||
], () => {
|
||||
],
|
||||
() => {
|
||||
if (animationId.value) {
|
||||
cancelAnimationFrame(animationId.value)
|
||||
cancelAnimationFrame(animationId.value);
|
||||
}
|
||||
animationId.value = requestAnimationFrame(draw)
|
||||
})
|
||||
animationId.value = requestAnimationFrame(draw);
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -3,161 +3,164 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, watch, computed } from 'vue'
|
||||
import { ref, onMounted, onUnmounted, watch, computed } from 'vue';
|
||||
|
||||
interface Props {
|
||||
to: number
|
||||
from?: number
|
||||
direction?: "up" | "down"
|
||||
delay?: number
|
||||
duration?: number
|
||||
className?: string
|
||||
startWhen?: boolean
|
||||
separator?: string
|
||||
onStart?: () => void
|
||||
onEnd?: () => void
|
||||
to: number;
|
||||
from?: number;
|
||||
direction?: 'up' | 'down';
|
||||
delay?: number;
|
||||
duration?: number;
|
||||
className?: string;
|
||||
startWhen?: boolean;
|
||||
separator?: string;
|
||||
onStart?: () => void;
|
||||
onEnd?: () => void;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
from: 0,
|
||||
direction: "up",
|
||||
direction: 'up',
|
||||
delay: 0,
|
||||
duration: 2,
|
||||
className: "",
|
||||
className: '',
|
||||
startWhen: true,
|
||||
separator: ""
|
||||
})
|
||||
separator: ''
|
||||
});
|
||||
|
||||
const elementRef = ref<HTMLSpanElement | null>(null)
|
||||
const currentValue = ref(props.direction === "down" ? props.to : props.from)
|
||||
const isInView = ref(false)
|
||||
const animationId = ref<number | null>(null)
|
||||
const hasStarted = ref(false)
|
||||
const elementRef = ref<HTMLSpanElement | null>(null);
|
||||
const currentValue = ref(props.direction === 'down' ? props.to : props.from);
|
||||
const isInView = ref(false);
|
||||
const animationId = ref<number | null>(null);
|
||||
const hasStarted = ref(false);
|
||||
|
||||
let intersectionObserver: IntersectionObserver | null = null
|
||||
let intersectionObserver: IntersectionObserver | null = null;
|
||||
|
||||
const damping = computed(() => 20 + 40 * (1 / props.duration))
|
||||
const stiffness = computed(() => 100 * (1 / props.duration))
|
||||
const damping = computed(() => 20 + 40 * (1 / props.duration));
|
||||
const stiffness = computed(() => 100 * (1 / props.duration));
|
||||
|
||||
let velocity = 0
|
||||
let startTime = 0
|
||||
let velocity = 0;
|
||||
let startTime = 0;
|
||||
|
||||
const formatNumber = (value: number) => {
|
||||
const options = {
|
||||
useGrouping: !!props.separator,
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0,
|
||||
}
|
||||
maximumFractionDigits: 0
|
||||
};
|
||||
|
||||
const formattedNumber = Intl.NumberFormat("en-US", options).format(
|
||||
Number(value.toFixed(0))
|
||||
)
|
||||
const formattedNumber = Intl.NumberFormat('en-US', options).format(Number(value.toFixed(0)));
|
||||
|
||||
return props.separator
|
||||
? formattedNumber.replace(/,/g, props.separator)
|
||||
: formattedNumber
|
||||
}
|
||||
return props.separator ? formattedNumber.replace(/,/g, props.separator) : formattedNumber;
|
||||
};
|
||||
|
||||
const updateDisplay = () => {
|
||||
if (elementRef.value) {
|
||||
elementRef.value.textContent = formatNumber(currentValue.value)
|
||||
}
|
||||
elementRef.value.textContent = formatNumber(currentValue.value);
|
||||
}
|
||||
};
|
||||
|
||||
const springAnimation = (timestamp: number) => {
|
||||
if (!startTime) startTime = timestamp
|
||||
if (!startTime) startTime = timestamp;
|
||||
|
||||
const target = props.direction === "down" ? props.from : props.to
|
||||
const current = currentValue.value
|
||||
const target = props.direction === 'down' ? props.from : props.to;
|
||||
const current = currentValue.value;
|
||||
|
||||
const displacement = target - current
|
||||
const springForce = displacement * stiffness.value
|
||||
const dampingForce = velocity * damping.value
|
||||
const acceleration = springForce - dampingForce
|
||||
const displacement = target - current;
|
||||
const springForce = displacement * stiffness.value;
|
||||
const dampingForce = velocity * damping.value;
|
||||
const acceleration = springForce - dampingForce;
|
||||
|
||||
velocity += acceleration * 0.016 // Assuming 60fps
|
||||
currentValue.value += velocity * 0.016
|
||||
velocity += acceleration * 0.016; // Assuming 60fps
|
||||
currentValue.value += velocity * 0.016;
|
||||
|
||||
updateDisplay()
|
||||
updateDisplay();
|
||||
|
||||
if (Math.abs(displacement) > 0.01 || Math.abs(velocity) > 0.01) {
|
||||
animationId.value = requestAnimationFrame(springAnimation)
|
||||
animationId.value = requestAnimationFrame(springAnimation);
|
||||
} else {
|
||||
currentValue.value = target
|
||||
updateDisplay()
|
||||
animationId.value = null
|
||||
currentValue.value = target;
|
||||
updateDisplay();
|
||||
animationId.value = null;
|
||||
|
||||
if (props.onEnd) {
|
||||
props.onEnd()
|
||||
}
|
||||
props.onEnd();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const startAnimation = () => {
|
||||
if (hasStarted.value || !isInView.value || !props.startWhen) return
|
||||
if (hasStarted.value || !isInView.value || !props.startWhen) return;
|
||||
|
||||
hasStarted.value = true
|
||||
hasStarted.value = true;
|
||||
|
||||
if (props.onStart) {
|
||||
props.onStart()
|
||||
props.onStart();
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
startTime = 0
|
||||
velocity = 0
|
||||
animationId.value = requestAnimationFrame(springAnimation)
|
||||
}, props.delay * 1000)
|
||||
}
|
||||
startTime = 0;
|
||||
velocity = 0;
|
||||
animationId.value = requestAnimationFrame(springAnimation);
|
||||
}, props.delay * 1000);
|
||||
};
|
||||
|
||||
const setupIntersectionObserver = () => {
|
||||
if (!elementRef.value) return
|
||||
if (!elementRef.value) return;
|
||||
|
||||
intersectionObserver = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry.isIntersecting && !isInView.value) {
|
||||
isInView.value = true
|
||||
startAnimation()
|
||||
isInView.value = true;
|
||||
startAnimation();
|
||||
}
|
||||
},
|
||||
{
|
||||
threshold: 0,
|
||||
rootMargin: "0px"
|
||||
rootMargin: '0px'
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
intersectionObserver.observe(elementRef.value)
|
||||
}
|
||||
intersectionObserver.observe(elementRef.value);
|
||||
};
|
||||
|
||||
const cleanup = () => {
|
||||
if (animationId.value) {
|
||||
cancelAnimationFrame(animationId.value)
|
||||
animationId.value = null
|
||||
cancelAnimationFrame(animationId.value);
|
||||
animationId.value = null;
|
||||
}
|
||||
|
||||
if (intersectionObserver) {
|
||||
intersectionObserver.disconnect()
|
||||
intersectionObserver = null
|
||||
}
|
||||
intersectionObserver.disconnect();
|
||||
intersectionObserver = null;
|
||||
}
|
||||
};
|
||||
|
||||
watch([() => props.from, () => props.to, () => props.direction], () => {
|
||||
currentValue.value = props.direction === "down" ? props.to : props.from
|
||||
updateDisplay()
|
||||
hasStarted.value = false
|
||||
}, { immediate: true })
|
||||
watch(
|
||||
[() => props.from, () => props.to, () => props.direction],
|
||||
() => {
|
||||
currentValue.value = props.direction === 'down' ? props.to : props.from;
|
||||
updateDisplay();
|
||||
hasStarted.value = false;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
watch(() => props.startWhen, () => {
|
||||
watch(
|
||||
() => props.startWhen,
|
||||
() => {
|
||||
if (props.startWhen && isInView.value && !hasStarted.value) {
|
||||
startAnimation()
|
||||
startAnimation();
|
||||
}
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
updateDisplay()
|
||||
setupIntersectionObserver()
|
||||
})
|
||||
updateDisplay();
|
||||
setupIntersectionObserver();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
cleanup()
|
||||
})
|
||||
cleanup();
|
||||
});
|
||||
</script>
|
||||
@@ -2,46 +2,74 @@
|
||||
<div class="relative w-1/2 max-md:w-11/12 aspect-square" :style="wrapperStyle">
|
||||
<div ref="sceneRef" class="grid w-full h-full" :style="sceneStyle">
|
||||
<template v-for="(_, r) in cells" :key="`row-${r}`">
|
||||
<div v-for="(__, c) in cells" :key="`${r}-${c}`"
|
||||
class="cube relative w-full h-full aspect-square [transform-style:preserve-3d]" :data-row="r" :data-col="c">
|
||||
<div
|
||||
v-for="(__, c) in cells"
|
||||
:key="`${r}-${c}`"
|
||||
class="cube relative w-full h-full aspect-square [transform-style:preserve-3d]"
|
||||
:data-row="r"
|
||||
:data-col="c"
|
||||
>
|
||||
<span class="absolute pointer-events-none -inset-9" />
|
||||
|
||||
<div class="cube-face absolute inset-0 flex items-center justify-center" :style="{
|
||||
<div
|
||||
class="cube-face absolute inset-0 flex items-center justify-center"
|
||||
:style="{
|
||||
background: 'var(--cube-face-bg)',
|
||||
border: 'var(--cube-face-border)',
|
||||
boxShadow: 'var(--cube-face-shadow)',
|
||||
transform: 'translateY(-50%) rotateX(90deg)',
|
||||
}" />
|
||||
<div class="cube-face absolute inset-0 flex items-center justify-center" :style="{
|
||||
transform: 'translateY(-50%) rotateX(90deg)'
|
||||
}"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="cube-face absolute inset-0 flex items-center justify-center"
|
||||
:style="{
|
||||
background: 'var(--cube-face-bg)',
|
||||
border: 'var(--cube-face-border)',
|
||||
boxShadow: 'var(--cube-face-shadow)',
|
||||
transform: 'translateY(50%) rotateX(-90deg)',
|
||||
}" />
|
||||
<div class="cube-face absolute inset-0 flex items-center justify-center" :style="{
|
||||
transform: 'translateY(50%) rotateX(-90deg)'
|
||||
}"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="cube-face absolute inset-0 flex items-center justify-center"
|
||||
:style="{
|
||||
background: 'var(--cube-face-bg)',
|
||||
border: 'var(--cube-face-border)',
|
||||
boxShadow: 'var(--cube-face-shadow)',
|
||||
transform: 'translateX(-50%) rotateY(-90deg)',
|
||||
}" />
|
||||
<div class="cube-face absolute inset-0 flex items-center justify-center" :style="{
|
||||
transform: 'translateX(-50%) rotateY(-90deg)'
|
||||
}"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="cube-face absolute inset-0 flex items-center justify-center"
|
||||
:style="{
|
||||
background: 'var(--cube-face-bg)',
|
||||
border: 'var(--cube-face-border)',
|
||||
boxShadow: 'var(--cube-face-shadow)',
|
||||
transform: 'translateX(50%) rotateY(90deg)',
|
||||
}" />
|
||||
<div class="cube-face absolute inset-0 flex items-center justify-center" :style="{
|
||||
transform: 'translateX(50%) rotateY(90deg)'
|
||||
}"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="cube-face absolute inset-0 flex items-center justify-center"
|
||||
:style="{
|
||||
background: 'var(--cube-face-bg)',
|
||||
border: 'var(--cube-face-border)',
|
||||
boxShadow: 'var(--cube-face-shadow)',
|
||||
transform: 'rotateY(-90deg) translateX(50%) rotateY(90deg)',
|
||||
}" />
|
||||
<div class="cube-face absolute inset-0 flex items-center justify-center" :style="{
|
||||
transform: 'rotateY(-90deg) translateX(50%) rotateY(90deg)'
|
||||
}"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="cube-face absolute inset-0 flex items-center justify-center"
|
||||
:style="{
|
||||
background: 'var(--cube-face-bg)',
|
||||
border: 'var(--cube-face-border)',
|
||||
boxShadow: 'var(--cube-face-shadow)',
|
||||
transform: 'rotateY(90deg) translateX(-50%) rotateY(-90deg)',
|
||||
}" />
|
||||
transform: 'rotateY(90deg) translateX(-50%) rotateY(-90deg)'
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@@ -49,34 +77,34 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted, withDefaults } from 'vue'
|
||||
import gsap from 'gsap'
|
||||
import { ref, computed, onMounted, onUnmounted, withDefaults } from 'vue';
|
||||
import gsap from 'gsap';
|
||||
|
||||
interface Gap {
|
||||
row: number
|
||||
col: number
|
||||
row: number;
|
||||
col: number;
|
||||
}
|
||||
|
||||
interface Duration {
|
||||
enter: number
|
||||
leave: number
|
||||
enter: number;
|
||||
leave: number;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
gridSize?: number
|
||||
cubeSize?: number
|
||||
maxAngle?: number
|
||||
radius?: number
|
||||
easing?: gsap.EaseString
|
||||
duration?: Duration
|
||||
cellGap?: number | Gap
|
||||
borderStyle?: string
|
||||
faceColor?: string
|
||||
shadow?: boolean | string
|
||||
autoAnimate?: boolean
|
||||
rippleOnClick?: boolean
|
||||
rippleColor?: string
|
||||
rippleSpeed?: number
|
||||
gridSize?: number;
|
||||
cubeSize?: number;
|
||||
maxAngle?: number;
|
||||
radius?: number;
|
||||
easing?: gsap.EaseString;
|
||||
duration?: Duration;
|
||||
cellGap?: number | Gap;
|
||||
borderStyle?: string;
|
||||
faceColor?: string;
|
||||
shadow?: boolean | string;
|
||||
autoAnimate?: boolean;
|
||||
rippleOnClick?: boolean;
|
||||
rippleColor?: string;
|
||||
rippleSpeed?: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -91,37 +119,37 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
autoAnimate: true,
|
||||
rippleOnClick: true,
|
||||
rippleColor: '#fff',
|
||||
rippleSpeed: 2,
|
||||
})
|
||||
rippleSpeed: 2
|
||||
});
|
||||
|
||||
const sceneRef = ref<HTMLDivElement | null>(null)
|
||||
const rafRef = ref<number | null>(null)
|
||||
const idleTimerRef = ref<number | null>(null)
|
||||
const userActiveRef = ref(false)
|
||||
const simPosRef = ref<{ x: number; y: number }>({ x: 0, y: 0 })
|
||||
const simTargetRef = ref<{ x: number; y: number }>({ x: 0, y: 0 })
|
||||
const simRAFRef = ref<number | null>(null)
|
||||
const sceneRef = ref<HTMLDivElement | null>(null);
|
||||
const rafRef = ref<number | null>(null);
|
||||
const idleTimerRef = ref<number | null>(null);
|
||||
const userActiveRef = ref(false);
|
||||
const simPosRef = ref<{ x: number; y: number }>({ x: 0, y: 0 });
|
||||
const simTargetRef = ref<{ x: number; y: number }>({ x: 0, y: 0 });
|
||||
const simRAFRef = ref<number | null>(null);
|
||||
|
||||
const colGap = computed(() => {
|
||||
return typeof props.cellGap === 'number'
|
||||
? `${props.cellGap}px`
|
||||
: (props.cellGap as Gap)?.col !== undefined
|
||||
? `${(props.cellGap as Gap).col}px`
|
||||
: '5%'
|
||||
})
|
||||
: '5%';
|
||||
});
|
||||
|
||||
const rowGap = computed(() => {
|
||||
return typeof props.cellGap === 'number'
|
||||
? `${props.cellGap}px`
|
||||
: (props.cellGap as Gap)?.row !== undefined
|
||||
? `${(props.cellGap as Gap).row}px`
|
||||
: '5%'
|
||||
})
|
||||
: '5%';
|
||||
});
|
||||
|
||||
const enterDur = computed(() => props.duration.enter)
|
||||
const leaveDur = computed(() => props.duration.leave)
|
||||
const enterDur = computed(() => props.duration.enter);
|
||||
const leaveDur = computed(() => props.duration.leave);
|
||||
|
||||
const cells = computed(() => Array.from({ length: props.gridSize }))
|
||||
const cells = computed(() => Array.from({ length: props.gridSize }));
|
||||
|
||||
const sceneStyle = computed(() => ({
|
||||
gridTemplateColumns: props.cubeSize
|
||||
@@ -133,189 +161,184 @@ const sceneStyle = computed(() => ({
|
||||
columnGap: colGap.value,
|
||||
rowGap: rowGap.value,
|
||||
perspective: '99999999px',
|
||||
gridAutoRows: '1fr',
|
||||
}))
|
||||
gridAutoRows: '1fr'
|
||||
}));
|
||||
|
||||
const wrapperStyle = computed(() => ({
|
||||
'--cube-face-border': props.borderStyle,
|
||||
'--cube-face-bg': props.faceColor,
|
||||
'--cube-face-shadow':
|
||||
props.shadow === true ? '0 0 6px rgba(0,0,0,.5)' : props.shadow || 'none',
|
||||
'--cube-face-shadow': props.shadow === true ? '0 0 6px rgba(0,0,0,.5)' : props.shadow || 'none',
|
||||
...(props.cubeSize
|
||||
? {
|
||||
width: `${props.gridSize * props.cubeSize}px`,
|
||||
height: `${props.gridSize * props.cubeSize}px`,
|
||||
height: `${props.gridSize * props.cubeSize}px`
|
||||
}
|
||||
: {}),
|
||||
}))
|
||||
: {})
|
||||
}));
|
||||
|
||||
const tiltAt = (rowCenter: number, colCenter: number) => {
|
||||
if (!sceneRef.value) return
|
||||
if (!sceneRef.value) return;
|
||||
|
||||
sceneRef.value.querySelectorAll<HTMLDivElement>('.cube').forEach((cube) => {
|
||||
const r = +(cube.dataset.row!)
|
||||
const c = +(cube.dataset.col!)
|
||||
const dist = Math.hypot(r - rowCenter, c - colCenter)
|
||||
sceneRef.value.querySelectorAll<HTMLDivElement>('.cube').forEach(cube => {
|
||||
const r = +cube.dataset.row!;
|
||||
const c = +cube.dataset.col!;
|
||||
const dist = Math.hypot(r - rowCenter, c - colCenter);
|
||||
|
||||
if (dist <= props.radius) {
|
||||
const pct = 1 - dist / props.radius
|
||||
const angle = pct * props.maxAngle
|
||||
const pct = 1 - dist / props.radius;
|
||||
const angle = pct * props.maxAngle;
|
||||
gsap.to(cube, {
|
||||
duration: enterDur.value,
|
||||
ease: props.easing,
|
||||
overwrite: true,
|
||||
rotateX: -angle,
|
||||
rotateY: angle,
|
||||
})
|
||||
rotateY: angle
|
||||
});
|
||||
} else {
|
||||
gsap.to(cube, {
|
||||
duration: leaveDur.value,
|
||||
ease: 'power3.out',
|
||||
overwrite: true,
|
||||
rotateX: 0,
|
||||
rotateY: 0,
|
||||
})
|
||||
}
|
||||
})
|
||||
rotateY: 0
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onPointerMove = (e: PointerEvent) => {
|
||||
userActiveRef.value = true
|
||||
if (idleTimerRef.value) clearTimeout(idleTimerRef.value)
|
||||
userActiveRef.value = true;
|
||||
if (idleTimerRef.value) clearTimeout(idleTimerRef.value);
|
||||
|
||||
const rect = sceneRef.value!.getBoundingClientRect()
|
||||
const cellW = rect.width / props.gridSize
|
||||
const cellH = rect.height / props.gridSize
|
||||
const colCenter = (e.clientX - rect.left) / cellW
|
||||
const rowCenter = (e.clientY - rect.top) / cellH
|
||||
const rect = sceneRef.value!.getBoundingClientRect();
|
||||
const cellW = rect.width / props.gridSize;
|
||||
const cellH = rect.height / props.gridSize;
|
||||
const colCenter = (e.clientX - rect.left) / cellW;
|
||||
const rowCenter = (e.clientY - rect.top) / cellH;
|
||||
|
||||
if (rafRef.value) cancelAnimationFrame(rafRef.value)
|
||||
rafRef.value = requestAnimationFrame(() =>
|
||||
tiltAt(rowCenter, colCenter)
|
||||
)
|
||||
if (rafRef.value) cancelAnimationFrame(rafRef.value);
|
||||
rafRef.value = requestAnimationFrame(() => tiltAt(rowCenter, colCenter));
|
||||
|
||||
idleTimerRef.value = setTimeout(() => {
|
||||
userActiveRef.value = false
|
||||
}, 3000)
|
||||
}
|
||||
userActiveRef.value = false;
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
const resetAll = () => {
|
||||
if (!sceneRef.value) return
|
||||
sceneRef.value.querySelectorAll<HTMLDivElement>('.cube').forEach((cube) =>
|
||||
if (!sceneRef.value) return;
|
||||
sceneRef.value.querySelectorAll<HTMLDivElement>('.cube').forEach(cube =>
|
||||
gsap.to(cube, {
|
||||
duration: leaveDur.value,
|
||||
rotateX: 0,
|
||||
rotateY: 0,
|
||||
ease: 'power3.out',
|
||||
ease: 'power3.out'
|
||||
})
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const onClick = (e: MouseEvent) => {
|
||||
if (!props.rippleOnClick || !sceneRef.value) return
|
||||
if (!props.rippleOnClick || !sceneRef.value) return;
|
||||
|
||||
const rect = sceneRef.value.getBoundingClientRect()
|
||||
const cellW = rect.width / props.gridSize
|
||||
const cellH = rect.height / props.gridSize
|
||||
const colHit = Math.floor((e.clientX - rect.left) / cellW)
|
||||
const rowHit = Math.floor((e.clientY - rect.top) / cellH)
|
||||
const rect = sceneRef.value.getBoundingClientRect();
|
||||
const cellW = rect.width / props.gridSize;
|
||||
const cellH = rect.height / props.gridSize;
|
||||
const colHit = Math.floor((e.clientX - rect.left) / cellW);
|
||||
const rowHit = Math.floor((e.clientY - rect.top) / cellH);
|
||||
|
||||
const baseRingDelay = 0.15
|
||||
const baseAnimDur = 0.3
|
||||
const baseHold = 0.6
|
||||
const baseRingDelay = 0.15;
|
||||
const baseAnimDur = 0.3;
|
||||
const baseHold = 0.6;
|
||||
|
||||
const spreadDelay = baseRingDelay / props.rippleSpeed
|
||||
const animDuration = baseAnimDur / props.rippleSpeed
|
||||
const holdTime = baseHold / props.rippleSpeed
|
||||
const spreadDelay = baseRingDelay / props.rippleSpeed;
|
||||
const animDuration = baseAnimDur / props.rippleSpeed;
|
||||
const holdTime = baseHold / props.rippleSpeed;
|
||||
|
||||
const rings: Record<number, HTMLDivElement[]> = {}
|
||||
sceneRef.value.querySelectorAll<HTMLDivElement>('.cube').forEach((cube) => {
|
||||
const r = +(cube.dataset.row!)
|
||||
const c = +(cube.dataset.col!)
|
||||
const dist = Math.hypot(r - rowHit, c - colHit)
|
||||
const ring = Math.round(dist)
|
||||
if (!rings[ring]) rings[ring] = []
|
||||
rings[ring].push(cube)
|
||||
})
|
||||
const rings: Record<number, HTMLDivElement[]> = {};
|
||||
sceneRef.value.querySelectorAll<HTMLDivElement>('.cube').forEach(cube => {
|
||||
const r = +cube.dataset.row!;
|
||||
const c = +cube.dataset.col!;
|
||||
const dist = Math.hypot(r - rowHit, c - colHit);
|
||||
const ring = Math.round(dist);
|
||||
if (!rings[ring]) rings[ring] = [];
|
||||
rings[ring].push(cube);
|
||||
});
|
||||
|
||||
Object.keys(rings)
|
||||
.map(Number)
|
||||
.sort((a, b) => a - b)
|
||||
.forEach((ring) => {
|
||||
const delay = ring * spreadDelay
|
||||
const faces = rings[ring].flatMap((cube) =>
|
||||
Array.from(cube.querySelectorAll<HTMLElement>('.cube-face'))
|
||||
)
|
||||
.forEach(ring => {
|
||||
const delay = ring * spreadDelay;
|
||||
const faces = rings[ring].flatMap(cube => Array.from(cube.querySelectorAll<HTMLElement>('.cube-face')));
|
||||
|
||||
gsap.to(faces, {
|
||||
backgroundColor: props.rippleColor,
|
||||
duration: animDuration,
|
||||
delay,
|
||||
ease: 'power3.out',
|
||||
})
|
||||
ease: 'power3.out'
|
||||
});
|
||||
gsap.to(faces, {
|
||||
backgroundColor: props.faceColor,
|
||||
duration: animDuration,
|
||||
delay: delay + animDuration + holdTime,
|
||||
ease: 'power3.out',
|
||||
})
|
||||
})
|
||||
}
|
||||
ease: 'power3.out'
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const startAutoAnimation = () => {
|
||||
if (!props.autoAnimate || !sceneRef.value) return
|
||||
if (!props.autoAnimate || !sceneRef.value) return;
|
||||
|
||||
simPosRef.value = {
|
||||
x: Math.random() * props.gridSize,
|
||||
y: Math.random() * props.gridSize,
|
||||
}
|
||||
y: Math.random() * props.gridSize
|
||||
};
|
||||
simTargetRef.value = {
|
||||
x: Math.random() * props.gridSize,
|
||||
y: Math.random() * props.gridSize,
|
||||
}
|
||||
y: Math.random() * props.gridSize
|
||||
};
|
||||
|
||||
const speed = 0.02
|
||||
const speed = 0.02;
|
||||
const loop = () => {
|
||||
if (!userActiveRef.value) {
|
||||
const pos = simPosRef.value
|
||||
const tgt = simTargetRef.value
|
||||
pos.x += (tgt.x - pos.x) * speed
|
||||
pos.y += (tgt.y - pos.y) * speed
|
||||
tiltAt(pos.y, pos.x)
|
||||
const pos = simPosRef.value;
|
||||
const tgt = simTargetRef.value;
|
||||
pos.x += (tgt.x - pos.x) * speed;
|
||||
pos.y += (tgt.y - pos.y) * speed;
|
||||
tiltAt(pos.y, pos.x);
|
||||
|
||||
if (Math.hypot(pos.x - tgt.x, pos.y - tgt.y) < 0.1) {
|
||||
simTargetRef.value = {
|
||||
x: Math.random() * props.gridSize,
|
||||
y: Math.random() * props.gridSize,
|
||||
y: Math.random() * props.gridSize
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
simRAFRef.value = requestAnimationFrame(loop)
|
||||
}
|
||||
simRAFRef.value = requestAnimationFrame(loop)
|
||||
}
|
||||
simRAFRef.value = requestAnimationFrame(loop);
|
||||
};
|
||||
simRAFRef.value = requestAnimationFrame(loop);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
const el = sceneRef.value
|
||||
if (!el) return
|
||||
const el = sceneRef.value;
|
||||
if (!el) return;
|
||||
|
||||
el.addEventListener('pointermove', onPointerMove)
|
||||
el.addEventListener('pointerleave', resetAll)
|
||||
el.addEventListener('click', onClick)
|
||||
el.addEventListener('pointermove', onPointerMove);
|
||||
el.addEventListener('pointerleave', resetAll);
|
||||
el.addEventListener('click', onClick);
|
||||
|
||||
startAutoAnimation()
|
||||
})
|
||||
startAutoAnimation();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
const el = sceneRef.value
|
||||
const el = sceneRef.value;
|
||||
if (el) {
|
||||
el.removeEventListener('pointermove', onPointerMove)
|
||||
el.removeEventListener('pointerleave', resetAll)
|
||||
el.removeEventListener('click', onClick)
|
||||
el.removeEventListener('pointermove', onPointerMove);
|
||||
el.removeEventListener('pointerleave', resetAll);
|
||||
el.removeEventListener('click', onClick);
|
||||
}
|
||||
|
||||
if (rafRef.value !== null) cancelAnimationFrame(rafRef.value)
|
||||
if (idleTimerRef.value !== null) clearTimeout(idleTimerRef.value)
|
||||
if (simRAFRef.value !== null) cancelAnimationFrame(simRAFRef.value)
|
||||
})
|
||||
if (rafRef.value !== null) cancelAnimationFrame(rafRef.value);
|
||||
if (idleTimerRef.value !== null) clearTimeout(idleTimerRef.value);
|
||||
if (simRAFRef.value !== null) cancelAnimationFrame(simRAFRef.value);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
:style="{
|
||||
opacity: inView ? 1 : initialOpacity,
|
||||
transition: `opacity ${duration}ms ${easing}, filter ${duration}ms ${easing}`,
|
||||
filter: blur ? (inView ? 'blur(0px)' : 'blur(10px)') : 'none',
|
||||
filter: blur ? (inView ? 'blur(0px)' : 'blur(10px)') : 'none'
|
||||
}"
|
||||
>
|
||||
<slot />
|
||||
@@ -13,16 +13,16 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
|
||||
interface Props {
|
||||
blur?: boolean
|
||||
duration?: number
|
||||
easing?: string
|
||||
delay?: number
|
||||
threshold?: number
|
||||
initialOpacity?: number
|
||||
className?: string
|
||||
blur?: boolean;
|
||||
duration?: number;
|
||||
easing?: string;
|
||||
delay?: number;
|
||||
threshold?: number;
|
||||
initialOpacity?: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -33,34 +33,34 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
threshold: 0.1,
|
||||
initialOpacity: 0,
|
||||
className: ''
|
||||
})
|
||||
});
|
||||
|
||||
const inView = ref(false)
|
||||
const elementRef = ref<HTMLDivElement | null>(null)
|
||||
let observer: IntersectionObserver | null = null
|
||||
const inView = ref(false);
|
||||
const elementRef = ref<HTMLDivElement | null>(null);
|
||||
let observer: IntersectionObserver | null = null;
|
||||
|
||||
onMounted(() => {
|
||||
const element = elementRef.value
|
||||
if (!element) return
|
||||
const element = elementRef.value;
|
||||
if (!element) return;
|
||||
|
||||
observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry.isIntersecting) {
|
||||
observer?.unobserve(element)
|
||||
observer?.unobserve(element);
|
||||
setTimeout(() => {
|
||||
inView.value = true
|
||||
}, props.delay)
|
||||
inView.value = true;
|
||||
}, props.delay);
|
||||
}
|
||||
},
|
||||
{ threshold: props.threshold }
|
||||
)
|
||||
);
|
||||
|
||||
observer.observe(element)
|
||||
})
|
||||
observer.observe(element);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (observer) {
|
||||
observer.disconnect()
|
||||
observer.disconnect();
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
@@ -1,20 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
interface GlareHoverProps {
|
||||
width?: string
|
||||
height?: string
|
||||
background?: string
|
||||
borderRadius?: string
|
||||
borderColor?: string
|
||||
glareColor?: string
|
||||
glareOpacity?: number
|
||||
glareAngle?: number
|
||||
glareSize?: number
|
||||
transitionDuration?: number
|
||||
playOnce?: boolean
|
||||
className?: string
|
||||
style?: Record<string, string | number>
|
||||
width?: string;
|
||||
height?: string;
|
||||
background?: string;
|
||||
borderRadius?: string;
|
||||
borderColor?: string;
|
||||
glareColor?: string;
|
||||
glareOpacity?: number;
|
||||
glareAngle?: number;
|
||||
glareSize?: number;
|
||||
transitionDuration?: number;
|
||||
playOnce?: boolean;
|
||||
className?: string;
|
||||
style?: Record<string, string | number>;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<GlareHoverProps>(), {
|
||||
@@ -31,28 +31,28 @@ const props = withDefaults(defineProps<GlareHoverProps>(), {
|
||||
playOnce: false,
|
||||
className: '',
|
||||
style: () => ({})
|
||||
})
|
||||
});
|
||||
|
||||
const overlayRef = ref<HTMLDivElement | null>(null)
|
||||
const overlayRef = ref<HTMLDivElement | null>(null);
|
||||
|
||||
const rgba = computed(() => {
|
||||
const hex = props.glareColor.replace('#', '')
|
||||
let result = props.glareColor
|
||||
const hex = props.glareColor.replace('#', '');
|
||||
let result = props.glareColor;
|
||||
|
||||
if (/^[\dA-Fa-f]{6}$/.test(hex)) {
|
||||
const r = parseInt(hex.slice(0, 2), 16)
|
||||
const g = parseInt(hex.slice(2, 4), 16)
|
||||
const b = parseInt(hex.slice(4, 6), 16)
|
||||
result = `rgba(${r}, ${g}, ${b}, ${props.glareOpacity})`
|
||||
const r = parseInt(hex.slice(0, 2), 16);
|
||||
const g = parseInt(hex.slice(2, 4), 16);
|
||||
const b = parseInt(hex.slice(4, 6), 16);
|
||||
result = `rgba(${r}, ${g}, ${b}, ${props.glareOpacity})`;
|
||||
} else if (/^[\dA-Fa-f]{3}$/.test(hex)) {
|
||||
const r = parseInt(hex[0] + hex[0], 16)
|
||||
const g = parseInt(hex[1] + hex[1], 16)
|
||||
const b = parseInt(hex[2] + hex[2], 16)
|
||||
result = `rgba(${r}, ${g}, ${b}, ${props.glareOpacity})`
|
||||
const r = parseInt(hex[0] + hex[0], 16);
|
||||
const g = parseInt(hex[1] + hex[1], 16);
|
||||
const b = parseInt(hex[2] + hex[2], 16);
|
||||
result = `rgba(${r}, ${g}, ${b}, ${props.glareOpacity})`;
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
return result;
|
||||
});
|
||||
|
||||
const overlayStyle = computed(() => ({
|
||||
position: 'absolute' as const,
|
||||
@@ -64,32 +64,32 @@ const overlayStyle = computed(() => ({
|
||||
backgroundSize: `${props.glareSize}% ${props.glareSize}%, 100% 100%`,
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: '-100% -100%, 0 0',
|
||||
pointerEvents: 'none' as const,
|
||||
}))
|
||||
pointerEvents: 'none' as const
|
||||
}));
|
||||
|
||||
const animateIn = () => {
|
||||
const el = overlayRef.value
|
||||
if (!el) return
|
||||
const el = overlayRef.value;
|
||||
if (!el) return;
|
||||
|
||||
el.style.transition = 'none'
|
||||
el.style.backgroundPosition = '-100% -100%, 0 0'
|
||||
void el.offsetHeight
|
||||
el.style.transition = `${props.transitionDuration}ms ease`
|
||||
el.style.backgroundPosition = '100% 100%, 0 0'
|
||||
}
|
||||
el.style.transition = 'none';
|
||||
el.style.backgroundPosition = '-100% -100%, 0 0';
|
||||
void el.offsetHeight;
|
||||
el.style.transition = `${props.transitionDuration}ms ease`;
|
||||
el.style.backgroundPosition = '100% 100%, 0 0';
|
||||
};
|
||||
|
||||
const animateOut = () => {
|
||||
const el = overlayRef.value
|
||||
if (!el) return
|
||||
const el = overlayRef.value;
|
||||
if (!el) return;
|
||||
|
||||
if (props.playOnce) {
|
||||
el.style.transition = 'none'
|
||||
el.style.backgroundPosition = '-100% -100%, 0 0'
|
||||
el.style.transition = 'none';
|
||||
el.style.backgroundPosition = '-100% -100%, 0 0';
|
||||
} else {
|
||||
el.style.transition = `${props.transitionDuration}ms ease`
|
||||
el.style.backgroundPosition = '-100% -100%, 0 0'
|
||||
}
|
||||
el.style.transition = `${props.transitionDuration}ms ease`;
|
||||
el.style.backgroundPosition = '-100% -100%, 0 0';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -101,12 +101,13 @@ const animateOut = () => {
|
||||
background: props.background,
|
||||
borderRadius: props.borderRadius,
|
||||
borderColor: props.borderColor,
|
||||
...props.style,
|
||||
...props.style
|
||||
}"
|
||||
@mouseenter="animateIn"
|
||||
@mouseleave="animateOut"
|
||||
>
|
||||
<div ref="overlayRef" :style="overlayStyle" />
|
||||
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user