mirror of
https://github.com/BeamMP/BeamMP-Website.git
synced 2026-04-18 06:10:07 +00:00
Rebuild website!
This commit is contained in:
121
src/components/AppFooter.vue
Normal file
121
src/components/AppFooter.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<script setup>
|
||||
import { Github, Twitter, Facebook } from 'lucide-vue-next'
|
||||
import { RouterLink } from 'vue-router'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<footer class="mt-auto py-8 border-t border-neutral-200 dark:border-neutral-800">
|
||||
<div class="max-w-6xl mx-auto px-4">
|
||||
<div class="flex flex-col md:flex-row items-center justify-between gap-6">
|
||||
<!-- Social Media Links -->
|
||||
<div class="flex items-center gap-4">
|
||||
<a
|
||||
href="https://www.reddit.com/r/BeamMP"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-neutral-500 hover:text-beammp-blue transition-colors dark:text-neutral-400 dark:hover:text-blue-400"
|
||||
aria-label="Reddit"
|
||||
>
|
||||
<svg
|
||||
class="w-5 h-5"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0zm5.01 4.744c.688 0 1.25.561 1.25 1.249a1.25 1.25 0 0 1-2.498.056l-2.597-.547-.8 3.747c1.824.07 3.48.632 4.674 1.488.308-.309.73-.491 1.207-.491.968 0 1.754.786 1.754 1.754 0 .716-.435 1.333-1.01 1.614a3.111 3.111 0 0 1 .042.52c0 2.694-3.13 4.87-7.004 4.87-3.874 0-7.004-2.176-7.004-4.87 0-.183.015-.366.043-.534A1.748 1.748 0 0 1 4.028 12c0-.968.786-1.754 1.754-1.754.463 0 .898.196 1.207.49 1.207-.883 2.878-1.43 4.744-1.487l.885-4.182a.342.342 0 0 1 .14-.197.35.35 0 0 1 .238-.042l2.906.617a1.214 1.214 0 0 1 1.108-.701zM9.25 12C8.561 12 8 12.562 8 13.25c0 .687.561 1.248 1.25 1.248.687 0 1.248-.561 1.248-1.249 0-.688-.561-1.249-1.249-1.249zm5.5 0c-.687 0-1.248.561-1.248 1.25 0 .687.561 1.248 1.249 1.248.688 0 1.249-.561 1.249-1.249 0-.687-.562-1.249-1.25-1.249zm-5.466 3.99a.327.327 0 0 0-.231.094.33.33 0 0 0 0 .463c.842.842 2.484.913 2.961.913.477 0 2.105-.056 2.961-.913a.361.361 0 0 0 .029-.463.33.33 0 0 0-.464 0c-.547.533-1.684.73-2.512.73-.828 0-1.979-.196-2.512-.73a.326.326 0 0 0-.232-.095z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
href="https://twitter.com/BeamMP_Mod_Team"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-neutral-500 hover:text-beammp-blue transition-colors dark:text-neutral-400 dark:hover:text-blue-400"
|
||||
aria-label="Twitter"
|
||||
>
|
||||
<Twitter class="w-5 h-5" />
|
||||
</a>
|
||||
<a
|
||||
href="https://www.facebook.com/BeamNGMP"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-neutral-500 hover:text-beammp-blue transition-colors dark:text-neutral-400 dark:hover:text-blue-400"
|
||||
aria-label="Facebook"
|
||||
>
|
||||
<Facebook class="w-5 h-5" />
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/BeamMP"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-neutral-500 hover:text-beammp-blue transition-colors dark:text-neutral-400 dark:hover:text-blue-400"
|
||||
aria-label="GitHub"
|
||||
>
|
||||
<Github class="w-5 h-5" />
|
||||
</a>
|
||||
<a
|
||||
href="https://discord.gg/beammp"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-neutral-500 hover:text-beammp-blue transition-colors dark:text-neutral-400 dark:hover:text-blue-400"
|
||||
aria-label="Discord"
|
||||
>
|
||||
<svg
|
||||
class="w-5 h-5"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<span class="hidden md:inline text-neutral-300 dark:text-neutral-600">|</span>
|
||||
<a
|
||||
href="https://www.patreon.com/BeamMP"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-xs text-orange-600 hover:text-orange-700 underline-offset-2 hover:underline dark:text-orange-400 dark:hover:text-orange-300"
|
||||
>
|
||||
Support on Patreon
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Copyright and Legal -->
|
||||
<div
|
||||
class="flex flex-col items-center md:items-end gap-2 text-xs text-neutral-600 dark:text-neutral-500"
|
||||
>
|
||||
<p>© 2019 - {{ new Date().getFullYear() }} | BeamMP Mod Team All Rights Reserved</p>
|
||||
<div class="flex gap-3">
|
||||
<RouterLink
|
||||
to="/about"
|
||||
class="text-neutral-700 hover:text-beammp-blue transition-colors dark:text-neutral-500 dark:hover:text-blue-400"
|
||||
>
|
||||
About
|
||||
</RouterLink>
|
||||
<span>·</span>
|
||||
<a
|
||||
href="https://forum.beammp.com/topic/95/privacy-policy-v1-0"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-neutral-700 hover:text-beammp-blue transition-colors dark:text-neutral-500 dark:hover:text-blue-400"
|
||||
>
|
||||
Privacy Policy
|
||||
</a>
|
||||
<span>·</span>
|
||||
<a
|
||||
href="https://forum.beammp.com/topic/94/terms-of-use-v1-0"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-neutral-700 hover:text-beammp-blue transition-colors dark:text-neutral-500 dark:hover:text-blue-400"
|
||||
>
|
||||
Terms & Conditions
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
261
src/components/AppNavigation.vue
Normal file
261
src/components/AppNavigation.vue
Normal file
@@ -0,0 +1,261 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { RouterLink } from 'vue-router'
|
||||
import {
|
||||
NavigationMenu,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuList,
|
||||
navigationMenuTriggerStyle,
|
||||
} from '@/components/ui/navigation-menu'
|
||||
import { cn } from '@/lib/utils'
|
||||
import ThemeToggle from '@/components/ThemeToggle.vue'
|
||||
import { Menu, X } from 'lucide-vue-next'
|
||||
|
||||
const mobileMenuOpen = ref(false)
|
||||
|
||||
function toggleMobileMenu() {
|
||||
mobileMenuOpen.value = !mobileMenuOpen.value
|
||||
}
|
||||
|
||||
function closeMobileMenu() {
|
||||
mobileMenuOpen.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header
|
||||
class="border-b border-neutral-200 dark:border-neutral-800 bg-white/80 dark:bg-neutral-900/70 backdrop-blur sticky top-0 z-20"
|
||||
>
|
||||
<nav class="max-w-6xl mx-auto px-4 h-16 flex items-center justify-between">
|
||||
<RouterLink to="/" class="flex items-center gap-2 shrink-0" @click="closeMobileMenu">
|
||||
<!-- Light mode logo -->
|
||||
<img src="/beammp-logo.png" alt="BeamMP Logo" class="h-10 w-auto shrink-0 dark:hidden" />
|
||||
<!-- Dark mode logo -->
|
||||
<img
|
||||
src="/beammp-logo-dark.png"
|
||||
alt="BeamMP Logo"
|
||||
class="h-10 w-auto shrink-0 hidden dark:block"
|
||||
/>
|
||||
</RouterLink>
|
||||
|
||||
<!-- Desktop Navigation -->
|
||||
<!-- Switch to mobile earlier (avoid logo crowding) -->
|
||||
<div class="hidden lg:flex items-center gap-4">
|
||||
<NavigationMenu>
|
||||
<NavigationMenuList>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink as-child>
|
||||
<a
|
||||
href="https://forum.beammp.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
:class="
|
||||
cn(
|
||||
navigationMenuTriggerStyle(),
|
||||
'bg-transparent text-neutral-900 hover:bg-neutral-100 hover:text-neutral-900 dark:bg-transparent dark:text-white dark:hover:bg-neutral-800 dark:hover:text-white'
|
||||
)
|
||||
"
|
||||
>
|
||||
Forum
|
||||
</a>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink as-child>
|
||||
<a
|
||||
href="https://docs.beammp.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
:class="
|
||||
cn(
|
||||
navigationMenuTriggerStyle(),
|
||||
'bg-transparent text-neutral-900 hover:bg-neutral-100 hover:text-neutral-900 dark:bg-transparent dark:text-white dark:hover:bg-neutral-800 dark:hover:text-white'
|
||||
)
|
||||
"
|
||||
>
|
||||
Docs
|
||||
</a>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink as-child>
|
||||
<RouterLink
|
||||
to="/communities"
|
||||
:class="
|
||||
cn(
|
||||
navigationMenuTriggerStyle(),
|
||||
'bg-transparent text-neutral-900 hover:bg-neutral-100 hover:text-neutral-900 dark:bg-transparent dark:text-white dark:hover:bg-neutral-800 dark:hover:text-white'
|
||||
)
|
||||
"
|
||||
>
|
||||
Communities
|
||||
</RouterLink>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink as-child>
|
||||
<RouterLink
|
||||
to="/servers"
|
||||
:class="
|
||||
cn(
|
||||
navigationMenuTriggerStyle(),
|
||||
'bg-transparent text-neutral-900 hover:bg-neutral-100 hover:text-neutral-900 dark:bg-transparent dark:text-white dark:hover:bg-neutral-800 dark:hover:text-white'
|
||||
)
|
||||
"
|
||||
>
|
||||
Servers
|
||||
</RouterLink>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink as-child>
|
||||
<RouterLink
|
||||
to="/stats"
|
||||
:class="
|
||||
cn(
|
||||
navigationMenuTriggerStyle(),
|
||||
'bg-transparent text-neutral-900 hover:bg-neutral-100 hover:text-neutral-900 dark:bg-transparent dark:text-white dark:hover:bg-neutral-800 dark:hover:text-white'
|
||||
)
|
||||
"
|
||||
>
|
||||
Statistics
|
||||
</RouterLink>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink as-child>
|
||||
<a
|
||||
href="https://github.com/BeamMP/BeamMP"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
:class="
|
||||
cn(
|
||||
navigationMenuTriggerStyle(),
|
||||
'bg-transparent text-neutral-900 hover:bg-neutral-100 hover:text-neutral-900 dark:bg-transparent dark:text-white dark:hover:bg-neutral-800 dark:hover:text-white'
|
||||
)
|
||||
"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink as-child>
|
||||
<a
|
||||
href="https://www.patreon.com/BeamMP"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
:class="
|
||||
cn(
|
||||
navigationMenuTriggerStyle(),
|
||||
'bg-transparent text-neutral-900 hover:bg-neutral-100 hover:text-neutral-900 dark:bg-transparent dark:text-white dark:hover:bg-neutral-800 dark:hover:text-white'
|
||||
)
|
||||
"
|
||||
>
|
||||
Patreon
|
||||
</a>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
</NavigationMenuList>
|
||||
</NavigationMenu>
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu Button and Theme Toggle -->
|
||||
<div class="flex lg:hidden items-center gap-2">
|
||||
<ThemeToggle />
|
||||
<button
|
||||
class="p-2 text-neutral-900 dark:text-white hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-md transition-colors"
|
||||
aria-label="Toggle menu"
|
||||
@click="toggleMobileMenu"
|
||||
>
|
||||
<Menu v-if="!mobileMenuOpen" class="w-6 h-6" />
|
||||
<X v-else class="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Mobile Menu -->
|
||||
<Transition
|
||||
enter-active-class="transition-all duration-200 ease-out"
|
||||
enter-from-class="opacity-0 -translate-y-2"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition-all duration-150 ease-in"
|
||||
leave-from-class="opacity-100 translate-y-0"
|
||||
leave-to-class="opacity-0 -translate-y-2"
|
||||
>
|
||||
<div
|
||||
v-if="mobileMenuOpen"
|
||||
class="md:hidden border-t border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900"
|
||||
>
|
||||
<div class="px-4 py-3 space-y-1">
|
||||
<RouterLink
|
||||
to="/about"
|
||||
class="block px-4 py-3 text-neutral-900 dark:text-white hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-md transition-colors"
|
||||
@click="closeMobileMenu"
|
||||
>
|
||||
About
|
||||
</RouterLink>
|
||||
<a
|
||||
href="https://forum.beammp.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="block px-4 py-3 text-neutral-900 dark:text-white hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-md transition-colors"
|
||||
@click="closeMobileMenu"
|
||||
>
|
||||
Forum
|
||||
</a>
|
||||
<a
|
||||
href="https://docs.beammp.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="block px-4 py-3 text-neutral-900 dark:text-white hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-md transition-colors"
|
||||
@click="closeMobileMenu"
|
||||
>
|
||||
Docs
|
||||
</a>
|
||||
<RouterLink
|
||||
to="/communities"
|
||||
class="block px-4 py-3 text-neutral-900 dark:text-white hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-md transition-colors"
|
||||
@click="closeMobileMenu"
|
||||
>
|
||||
Communities
|
||||
</RouterLink>
|
||||
<RouterLink
|
||||
to="/servers"
|
||||
class="block px-4 py-3 text-neutral-900 dark:text-white hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-md transition-colors"
|
||||
@click="closeMobileMenu"
|
||||
>
|
||||
Servers
|
||||
</RouterLink>
|
||||
<RouterLink
|
||||
to="/stats"
|
||||
class="block px-4 py-3 text-neutral-900 dark:text-white hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-md transition-colors"
|
||||
@click="closeMobileMenu"
|
||||
>
|
||||
Statistics
|
||||
</RouterLink>
|
||||
<a
|
||||
href="https://github.com/BeamMP/BeamMP"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="block px-4 py-3 text-neutral-900 dark:text-white hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-md transition-colors"
|
||||
@click="closeMobileMenu"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
<a
|
||||
href="https://www.patreon.com/BeamMP"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="block px-4 py-3 text-neutral-900 dark:text-white hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-md transition-colors"
|
||||
@click="closeMobileMenu"
|
||||
>
|
||||
Patreon
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</header>
|
||||
</template>
|
||||
78
src/components/ThemeToggle.vue
Normal file
78
src/components/ThemeToggle.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { Sun, Moon, Monitor } from 'lucide-vue-next'
|
||||
|
||||
const theme = ref('system')
|
||||
|
||||
const setTheme = (newTheme) => {
|
||||
theme.value = newTheme
|
||||
localStorage.setItem('theme', newTheme)
|
||||
applyTheme(newTheme)
|
||||
}
|
||||
|
||||
const applyTheme = (selectedTheme) => {
|
||||
const root = document.documentElement
|
||||
if (
|
||||
selectedTheme === 'dark' ||
|
||||
(selectedTheme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
||||
) {
|
||||
root.classList.add('dark')
|
||||
} else {
|
||||
root.classList.remove('dark')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const savedTheme = localStorage.getItem('theme') || 'system'
|
||||
theme.value = savedTheme
|
||||
applyTheme(savedTheme)
|
||||
|
||||
// Listen for system theme changes
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
||||
if (theme.value === 'system') {
|
||||
applyTheme('system')
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center gap-1 bg-neutral-200 dark:bg-neutral-800/50 rounded-lg p-1">
|
||||
<button
|
||||
:class="[
|
||||
'p-2 rounded transition-colors',
|
||||
theme === 'light'
|
||||
? 'bg-white shadow-sm text-neutral-900 dark:bg-neutral-700 dark:text-white'
|
||||
: 'text-neutral-600 hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-neutral-200',
|
||||
]"
|
||||
title="Light mode"
|
||||
@click="setTheme('light')"
|
||||
>
|
||||
<Sun class="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
:class="[
|
||||
'p-2 rounded transition-colors',
|
||||
theme === 'system'
|
||||
? 'bg-white shadow-sm text-neutral-900 dark:bg-neutral-700 dark:text-white'
|
||||
: 'text-neutral-600 hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-neutral-200',
|
||||
]"
|
||||
title="System theme"
|
||||
@click="setTheme('system')"
|
||||
>
|
||||
<Monitor class="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
:class="[
|
||||
'p-2 rounded transition-colors',
|
||||
theme === 'dark'
|
||||
? 'bg-white shadow-sm text-neutral-900 dark:bg-neutral-700 dark:text-white'
|
||||
: 'text-neutral-600 hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-neutral-200',
|
||||
]"
|
||||
title="Dark mode"
|
||||
@click="setTheme('dark')"
|
||||
>
|
||||
<Moon class="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
23
src/components/ui/button/Button.vue
Normal file
23
src/components/ui/button/Button.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<script setup>
|
||||
import { Primitive } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { buttonVariants } from '.'
|
||||
|
||||
const props = defineProps({
|
||||
variant: { type: null, required: false },
|
||||
size: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false, default: 'button' },
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Primitive
|
||||
:as="as"
|
||||
:as-child="asChild"
|
||||
:class="cn(buttonVariants({ variant, size }), props.class)"
|
||||
>
|
||||
<slot />
|
||||
</Primitive>
|
||||
</template>
|
||||
33
src/components/ui/button/index.js
Normal file
33
src/components/ui/button/index.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { cva } from 'class-variance-authority'
|
||||
|
||||
export { default as Button } from './Button.vue'
|
||||
|
||||
export const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
|
||||
destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
|
||||
outline:
|
||||
'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
|
||||
secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
|
||||
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
},
|
||||
size: {
|
||||
default: 'h-9 px-4 py-2',
|
||||
xs: 'h-7 rounded px-2',
|
||||
sm: 'h-8 rounded-md px-3 text-xs',
|
||||
lg: 'h-10 rounded-md px-8',
|
||||
icon: 'h-9 w-9',
|
||||
'icon-sm': 'size-8',
|
||||
'icon-lg': 'size-10',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
},
|
||||
}
|
||||
)
|
||||
38
src/components/ui/navigation-menu/NavigationMenu.vue
Normal file
38
src/components/ui/navigation-menu/NavigationMenu.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script setup>
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { NavigationMenuRoot, useForwardPropsEmits } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
import NavigationMenuViewport from './NavigationMenuViewport.vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: String, required: false },
|
||||
defaultValue: { type: String, required: false },
|
||||
dir: { type: String, required: false },
|
||||
orientation: { type: String, required: false },
|
||||
delayDuration: { type: Number, required: false },
|
||||
skipDelayDuration: { type: Number, required: false },
|
||||
disableClickTrigger: { type: Boolean, required: false },
|
||||
disableHoverTrigger: { type: Boolean, required: false },
|
||||
disablePointerLeaveClose: { type: Boolean, required: false },
|
||||
unmountOnHide: { type: Boolean, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
})
|
||||
|
||||
const emits = defineEmits(['update:modelValue'])
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NavigationMenuRoot
|
||||
v-bind="forwarded"
|
||||
:class="cn('relative z-10 flex max-w-max flex-1 items-center justify-center', props.class)"
|
||||
>
|
||||
<slot />
|
||||
<NavigationMenuViewport />
|
||||
</NavigationMenuRoot>
|
||||
</template>
|
||||
38
src/components/ui/navigation-menu/NavigationMenuContent.vue
Normal file
38
src/components/ui/navigation-menu/NavigationMenuContent.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script setup>
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { NavigationMenuContent, useForwardPropsEmits } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps({
|
||||
forceMount: { type: Boolean, required: false },
|
||||
disableOutsidePointerEvents: { type: Boolean, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
})
|
||||
|
||||
const emits = defineEmits([
|
||||
'escapeKeyDown',
|
||||
'pointerDownOutside',
|
||||
'focusOutside',
|
||||
'interactOutside',
|
||||
])
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NavigationMenuContent
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</NavigationMenuContent>
|
||||
</template>
|
||||
@@ -0,0 +1,30 @@
|
||||
<script setup>
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { NavigationMenuIndicator, useForwardProps } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps({
|
||||
forceMount: { type: Boolean, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
})
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NavigationMenuIndicator
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn(
|
||||
'top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<div class="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
|
||||
</NavigationMenuIndicator>
|
||||
</template>
|
||||
15
src/components/ui/navigation-menu/NavigationMenuItem.vue
Normal file
15
src/components/ui/navigation-menu/NavigationMenuItem.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup>
|
||||
import { NavigationMenuItem } from 'reka-ui'
|
||||
|
||||
const props = defineProps({
|
||||
value: { type: String, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NavigationMenuItem v-bind="props">
|
||||
<slot />
|
||||
</NavigationMenuItem>
|
||||
</template>
|
||||
18
src/components/ui/navigation-menu/NavigationMenuLink.vue
Normal file
18
src/components/ui/navigation-menu/NavigationMenuLink.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<script setup>
|
||||
import { NavigationMenuLink, useForwardPropsEmits } from 'reka-ui'
|
||||
|
||||
const props = defineProps({
|
||||
active: { type: Boolean, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
})
|
||||
const emits = defineEmits(['select'])
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NavigationMenuLink v-bind="forwarded">
|
||||
<slot />
|
||||
</NavigationMenuLink>
|
||||
</template>
|
||||
24
src/components/ui/navigation-menu/NavigationMenuList.vue
Normal file
24
src/components/ui/navigation-menu/NavigationMenuList.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<script setup>
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { NavigationMenuList, useForwardProps } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps({
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
})
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NavigationMenuList
|
||||
v-bind="forwardedProps"
|
||||
:class="cn('group flex flex-1 list-none items-center justify-center gap-x-1', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</NavigationMenuList>
|
||||
</template>
|
||||
31
src/components/ui/navigation-menu/NavigationMenuTrigger.vue
Normal file
31
src/components/ui/navigation-menu/NavigationMenuTrigger.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<script setup>
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { ChevronDown } from 'lucide-vue-next'
|
||||
import { NavigationMenuTrigger, useForwardProps } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { navigationMenuTriggerStyle } from '.'
|
||||
|
||||
const props = defineProps({
|
||||
disabled: { type: Boolean, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
})
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NavigationMenuTrigger
|
||||
v-bind="forwardedProps"
|
||||
:class="cn(navigationMenuTriggerStyle(), 'group', props.class)"
|
||||
>
|
||||
<slot />
|
||||
<ChevronDown
|
||||
class="relative top-px ml-1 h-3 w-3 transition duration-300 group-data-[state=open]:rotate-180"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</NavigationMenuTrigger>
|
||||
</template>
|
||||
31
src/components/ui/navigation-menu/NavigationMenuViewport.vue
Normal file
31
src/components/ui/navigation-menu/NavigationMenuViewport.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<script setup>
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { NavigationMenuViewport, useForwardProps } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps({
|
||||
forceMount: { type: Boolean, required: false },
|
||||
align: { type: String, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
})
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="absolute left-0 top-full flex justify-center">
|
||||
<NavigationMenuViewport
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn(
|
||||
'origin-top-center relative mt-1.5 h-[--reka-navigation-menu-viewport-height] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[--reka-navigation-menu-viewport-width] left-[var(--reka-navigation-menu-viewport-left)]',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
14
src/components/ui/navigation-menu/index.js
Normal file
14
src/components/ui/navigation-menu/index.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { cva } from 'class-variance-authority'
|
||||
|
||||
export { default as NavigationMenu } from './NavigationMenu.vue'
|
||||
export { default as NavigationMenuContent } from './NavigationMenuContent.vue'
|
||||
export { default as NavigationMenuIndicator } from './NavigationMenuIndicator.vue'
|
||||
export { default as NavigationMenuItem } from './NavigationMenuItem.vue'
|
||||
export { default as NavigationMenuLink } from './NavigationMenuLink.vue'
|
||||
export { default as NavigationMenuList } from './NavigationMenuList.vue'
|
||||
export { default as NavigationMenuTrigger } from './NavigationMenuTrigger.vue'
|
||||
export { default as NavigationMenuViewport } from './NavigationMenuViewport.vue'
|
||||
|
||||
export const navigationMenuTriggerStyle = cva(
|
||||
'group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50'
|
||||
)
|
||||
Reference in New Issue
Block a user