feat(stage-web): component for links

This commit is contained in:
Neko Ayaka
2025-03-09 12:22:18 +08:00
parent 8e68926052
commit b089468783
3 changed files with 184 additions and 158 deletions
@@ -0,0 +1,135 @@
<script setup lang="ts">
const props = defineProps<{
title: string
description: string
icon?: string
iconColor?: string
iconImage?: string
to: string
configured: boolean
}>()
</script>
<template>
<div
flex="~ col"
bg="neutral-200/50 dark:neutral-700"
border="neutral-100 dark:neutral-700 hover:primary-500 dark:hover:primary-400 solid 2"
drop-shadow="none hover:[0px_4px_4px_rgba(220,220,220,0.4)] active:[0px_0px_0px_rgba(220,220,220,0.25)] dark:hover:none"
class="menu-icon-status-item"
transition="all ease-in-out duration-200"
w-full of-hidden rounded-xl
>
<RouterLink
flex="~ row"
bg="neutral-50 dark:neutral-800"
transition="all ease-in-out duration-200"
relative w-full items-center overflow-hidden rounded-lg p-5 text-left
:to=" props.to"
>
<div z-1 flex-1>
<div
text-lg font-bold
class="menu-icon-status-item-title"
transition="all ease-in-out duration-200"
>
{{ props.title }}
</div>
<div
text="sm neutral-500 dark:neutral-400"
class="menu-icon-status-item-description"
transition="all ease-in-out duration-200"
>
<span>{{ props.description }}</span>
</div>
</div>
<template v-if=" props.icon">
<div
class="menu-icon-status-item-icon"
transition="all ease-in-out duration-500"
absolute right-0 size-16 translate-y-2
text="neutral-400/50 dark:neutral-600/50"
grayscale-100
:class="[props.icon]"
/>
</template>
<template v-if=" props.iconColor">
<div
class="menu-icon-status-item-icon-color"
transition="all ease-in-out duration-500"
absolute right-0 size-16 translate-y-2
text="neutral-400/50 dark:neutral-600/50"
grayscale-100
:class="[props.iconColor]"
/>
</template>
<template v-if=" props.iconImage">
<img
:src=" props.iconImage"
class="menu-icon-status-item-icon-image"
transition="all ease-in-out duration-500"
absolute right-0 size-16 translate-y-2
grayscale-100
>
</template>
</RouterLink>
<div p-2>
<div v-if=" props.configured" size-3 bg="green-500 dark:green-600" rounded-full />
<div v-else size-3 bg="neutral-400 dark:neutral-500" rounded-full />
</div>
</div>
</template>
<style scoped>
.menu-icon-status-item::after {
--at-apply: 'bg-dotted-[neutral-200] hover:bg-dotted-[primary-300/50] dark:bg-dotted-[neutral-700/80] dark:hover:bg-dotted-[primary-200/20]';
position: absolute;
inset: 0;
z-index: -1;
width: 100%;
height: 100%;
background-size: 10px 10px;
content: '';
mask-image: linear-gradient(165deg, white 30%, transparent 50%);
}
.menu-icon-status-item:hover .menu-icon-status-item-title {
--at-apply: text-primary-500;
}
.menu-icon-status-item:hover .menu-icon-status-item-description {
--at-apply: text-primary-500;
opacity: 0.8;
}
.menu-icon-status-item:hover .menu-icon-status-item-icon {
--at-apply: text-primary-500;
scale: 1.2;
opacity: 0.2;
filter: grayscale(0);
}
.menu-icon-status-item:hover .menu-icon-status-item-icon-color {
scale: 1.2;
filter: grayscale(0);
}
.menu-icon-status-item:hover .menu-icon-status-item-icon-image {
scale: 1.2;
filter: grayscale(0);
}
.dark .menu-icon-status-item:hover .menu-icon-status-item-title {
--at-apply: text-primary-400;
}
.dark .menu-icon-status-item:hover .menu-icon-status-item-description {
--at-apply: text-primary-400;
opacity: 0.8;
}
.dark .menu-icon-status-item:hover .menu-icon-status-item-icon {
--at-apply: text-primary-400;
opacity: 0.2;
}
</style>
@@ -3,6 +3,7 @@ import { computed } from 'vue'
import { useRouter } from 'vue-router'
import FactorioIcon from '../../../assets/icons/modules/games/factorio.png'
import IconStatusItem from '../../../components/Menu/IconStatusItem.vue'
const router = useRouter()
@@ -109,74 +110,17 @@ const modulesList = computed<Module[]>(() => [
</h1>
</div>
<div grid="~ cols-1 sm:cols-2 gap-4">
<div
v-for="module in modulesList" :key="module.id"
flex="~ col"
bg="neutral-200/50 dark:neutral-700"
border="neutral-100 dark:neutral-700 hover:primary-500 dark:hover:primary-400 solid 2"
drop-shadow="none hover:[0px_4px_4px_rgba(220,220,220,0.4)] active:[0px_0px_0px_rgba(220,220,220,0.25)] dark:hover:none"
class="menu-icon-item-modules"
transition="all ease-in-out duration-200"
w-full of-hidden rounded-xl
>
<RouterLink
flex="~ row"
bg="neutral-50 dark:neutral-800"
transition="all ease-in-out duration-200"
relative w-full items-center overflow-hidden rounded-lg p-5 text-left
:to="module.to"
>
<div z-1 flex-1>
<div
text-lg font-bold
class="menu-icon-item-modules-title"
transition="all ease-in-out duration-200"
>
{{ module.name }}
</div>
<div
text="sm neutral-500 dark:neutral-400"
class="menu-icon-item-modules-description"
transition="all ease-in-out duration-200"
>
<span>{{ module.description }}</span>
</div>
</div>
<template v-if="module.icon">
<div
class="menu-icon-item-modules-icon"
transition="all ease-in-out duration-500"
absolute right-0 size-16 translate-y-2
text="neutral-400/50 dark:neutral-600/50"
grayscale-100
:class="[module.icon]"
/>
</template>
<template v-if="module.iconColor">
<div
class="menu-icon-item-modules-icon-color"
transition="all ease-in-out duration-500"
absolute right-0 size-16 translate-y-2
text="neutral-400/50 dark:neutral-600/50"
grayscale-100
:class="[module.iconColor]"
/>
</template>
<template v-if="module.iconImage">
<img
:src="module.iconImage"
class="menu-icon-item-modules-icon-image"
transition="all ease-in-out duration-500"
absolute right-0 size-16 translate-y-2
grayscale-100
>
</template>
</RouterLink>
<div p-2>
<div v-if="module.configured" size-3 bg="green-500 dark:green-600" rounded-full />
<div v-else size-3 bg="neutral-400 dark:neutral-500" rounded-full />
</div>
</div>
<IconStatusItem
v-for="module in modulesList"
:key="module.id"
:title="module.name"
:description="module.description"
:icon="module.icon"
:icon-color="module.iconColor"
:icon-image="module.iconImage"
:to="module.to"
:configured="module.configured"
/>
</div>
<div fixed bottom-0 right-0 z--1 text="neutral-100/80 dark:neutral-500/20">
<div text="40" i-lucide:blocks translate-x-10 translate-y-10 />
@@ -188,57 +132,3 @@ meta:
stageTransition:
name: slide
</route>
<style scoped>
.menu-icon-item-modules::after {
--at-apply: 'bg-dotted-[neutral-200] hover:bg-dotted-[primary-300/50] dark:bg-dotted-[neutral-700/80] dark:hover:bg-dotted-[primary-200/20]';
position: absolute;
inset: 0;
z-index: -1;
width: 100%;
height: 100%;
background-size: 10px 10px;
content: '';
mask-image: linear-gradient(165deg, white 30%, transparent 50%);
}
.menu-icon-item-modules:hover .menu-icon-item-modules-title {
--at-apply: text-primary-500;
}
.menu-icon-item-modules:hover .menu-icon-item-modules-description {
--at-apply: text-primary-500;
opacity: 0.8;
}
.menu-icon-item-modules:hover .menu-icon-item-modules-icon {
--at-apply: text-primary-500;
scale: 1.2;
opacity: 0.2;
filter: grayscale(0);
}
.menu-icon-item-modules:hover .menu-icon-item-modules-icon-color {
scale: 1.2;
filter: grayscale(0);
}
.menu-icon-item-modules:hover .menu-icon-item-modules-icon-image {
scale: 1.2;
filter: grayscale(0);
}
.dark .menu-icon-item-modules:hover .menu-icon-item-modules-title {
--at-apply: text-primary-400;
}
.dark .menu-icon-item-modules:hover .menu-icon-item-modules-description {
--at-apply: text-primary-400;
opacity: 0.8;
}
.dark .menu-icon-item-modules:hover .menu-icon-item-modules-icon {
--at-apply: text-primary-400;
opacity: 0.2;
}
</style>
@@ -2,12 +2,17 @@
import { useProvidersStore } from '@proj-airi/stage-ui/stores'
import { storeToRefs } from 'pinia'
import { computed } from 'vue'
import { RouterLink, useRouter } from 'vue-router'
import { useRouter } from 'vue-router'
import IconStatusItem from '../../../components/Menu/IconStatusItem.vue'
interface ModelProvider {
id: string
name: string
description: string
icon?: string
iconColor?: string
iconImage?: string
to: string
configured: boolean
}
@@ -20,6 +25,7 @@ const providersList = computed<ModelProvider[]>(() => [
{
id: 'openrouter-ai',
name: 'OpenRouter',
description: 'openrouter.ai',
icon: 'i-lobe-icons:openrouter',
to: '/settings/providers/openrouter',
configured: configuredForOpenRouter.value,
@@ -27,6 +33,7 @@ const providersList = computed<ModelProvider[]>(() => [
{
id: 'openai',
name: 'OpenAI',
description: 'openai.com',
icon: 'i-lobe-icons:openai',
to: '/settings/providers/openai',
configured: false,
@@ -34,6 +41,7 @@ const providersList = computed<ModelProvider[]>(() => [
{
id: 'ollama-ai',
name: 'Ollama',
description: 'ollama.com',
icon: 'i-lobe-icons:ollama',
to: '/settings/providers/ollama',
configured: false,
@@ -41,13 +49,15 @@ const providersList = computed<ModelProvider[]>(() => [
{
id: 'vllm',
name: 'vLLM',
icon: 'i-lobe-icons:vllm-color',
description: 'vllm.ai',
iconColor: 'i-lobe-icons:vllm-color',
to: '/settings/providers/vllm',
configured: false,
},
{
id: 'elevenlabs',
name: 'ElevenLabs',
description: 'elevenlabs.io',
icon: 'i-simple-icons:elevenlabs',
to: '/settings/providers/elevenlabs',
configured: false,
@@ -55,6 +65,7 @@ const providersList = computed<ModelProvider[]>(() => [
{
id: 'xai',
name: 'xAI',
description: 'x.ai',
icon: 'i-lobe-icons:xai',
to: '/settings/providers/xai',
configured: false,
@@ -62,27 +73,31 @@ const providersList = computed<ModelProvider[]>(() => [
{
id: 'deepseek',
name: 'DeepSeek',
icon: 'i-lobe-icons:deepseek-color',
description: 'deepseek.com',
iconColor: 'i-lobe-icons:deepseek-color',
to: '/settings/providers/deepseek',
configured: false,
},
{
id: 'together-ai',
name: 'Together.ai',
icon: 'i-lobe-icons:together-color',
description: 'together.ai',
iconColor: 'i-lobe-icons:together-color',
to: '/settings/providers/together',
configured: false,
},
{
id: 'novita-ai',
name: 'Novita',
icon: 'i-lobe-icons:novita-color',
description: 'novita.ai',
iconColor: 'i-lobe-icons:novita-color',
to: '/settings/providers/novita',
configured: false,
},
{
id: 'fireworks-ai',
name: 'Fireworks.ai',
description: 'fireworks.ai',
icon: 'i-lobe-icons:fireworks',
to: '/settings/providers/fireworks',
configured: false,
@@ -90,20 +105,23 @@ const providersList = computed<ModelProvider[]>(() => [
{
id: 'cloudflare-workers-ai',
name: 'Cloudflare Workers AI',
icon: 'i-lobe-icons:cloudflare-color',
description: 'cloudflare.com',
iconColor: 'i-lobe-icons:cloudflare-color',
to: '/settings/providers/cloudflare',
configured: false,
},
{
id: 'mistral-ai',
name: 'Mistral',
icon: 'i-lobe-icons:mistral-color',
description: 'mistral.ai',
iconColor: 'i-lobe-icons:mistral-color',
to: '/settings/providers/mistral',
configured: false,
},
{
id: 'moonshot-ai',
name: 'Moonshot AI',
description: 'moonshot.ai',
icon: 'i-lobe-icons:moonshot',
to: '/settings/providers/moonshot',
configured: false,
@@ -125,35 +143,18 @@ const providersList = computed<ModelProvider[]>(() => [
</div>
</h1>
</div>
<div grid="~ cols-3 gap-2">
<div
v-for="provider in providersList" :key="provider.id"
bg="neutral-300/50 dark:neutral-600" w-full of-hidden rounded-xl
flex="~ col 1"
>
<RouterLink
:to="provider.to"
bg="neutral-100 dark:neutral-800"
hover="bg-neutral-200 dark:bg-neutral-700"
transition="all ease-in-out duration-250"
h-full w-full flex items-center gap-1.5 rounded-lg px-4 py-5 outline-none
class="[&_.provider-icon]:grayscale-100 [&_.provider-icon]:hover:grayscale-0"
>
<div flex="~ col 1" gap-1.5>
<div
:class="provider.icon" class="provider-icon size-10"
transition="filter duration-250 ease-in-out"
/>
<div>
{{ provider.name }}
</div>
</div>
</RouterLink>
<div p-2>
<div v-if="provider.configured" size-3 bg="green-500 dark:green-600" rounded-full />
<div v-else size-3 bg="neutral-400 dark:neutral-500" rounded-full />
</div>
</div>
<div grid="~ cols-2 gap-2">
<IconStatusItem
v-for="provider in providersList"
:key="provider.id"
:title="provider.name"
:description="provider.description"
:icon="provider.icon"
:icon-color="provider.iconColor"
:icon-image="provider.iconImage"
:to="provider.to"
:configured="provider.configured"
/>
</div>
<div fixed bottom-0 right-0 z--1 text="neutral-100/80 dark:neutral-500/20">
<div text="40" i-lucide:brain translate-x-10 translate-y-10 />