initial commit

This commit is contained in:
Zoe
2026-01-11 05:04:29 -06:00
commit 0877cc10bd
65 changed files with 5009 additions and 0 deletions
+82
View File
@@ -0,0 +1,82 @@
<script setup lang="ts">
const inputRef = ref<HTMLTextAreaElement | null>(null);
const inputValue = ref('');
const isFocused = ref(false);
const emit = defineEmits<{
submit: [value: string];
}>();
const props = defineProps({
loading: {
type: Boolean,
default: false
}
})
const handleSubmit = () => {
if (inputValue.value.trim()) {
emit('submit', inputValue.value);
inputValue.value = '';
}
};
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Enter') {
if (event.shiftKey) return;
if (event.ctrlKey || event.metaKey) {
if (inputRef.value === null) return;
// inset new line
let cursorPosition = inputRef.value.selectionStart;
if (cursorPosition === undefined) return;
if (cursorPosition !== inputRef.value.selectionEnd) return;
inputValue.value = inputValue.value.slice(0, cursorPosition) + "\n" + inputValue.value.slice(cursorPosition);
// inputRef.value.selectionStart = cursorPosition + 1;
return;
}
event.preventDefault();
handleSubmit();
}
};
let hasCommandKey = false;
if (import.meta.server) {
let headers = useRequestHeaders();
hasCommandKey = headers['user-agent']?.includes('Mac OS') ?? false;
} else {
hasCommandKey = navigator.userAgent.includes('Mac OS');
}
</script>
<template>
<div class="w-full max-w-full mx-auto">
<div class="relative flex flex-col gap-3 p-3 rounded-2xl border transition-border ease-in-out duration-300 bg-[var(--color-input)]
border-[var(--color-highlight)] focus-within:border-[var(--color-highlight-high)]">
<!-- Text Input -->
<div class="flex-1 min-w-0">
<textarea v-model="inputValue" ref="inputRef"
:placeholder="`Start something great. Press ${hasCommandKey ? '⌘ + Enter' : 'ctrl + Enter'} to insert a new line.`"
@focus="isFocused = true" @blur="isFocused = false" @keydown="handleKeyDown"
class="w-full bg-transparent text-[var(--color-text)] placeholder-white/50 resize-none outline-none text-[15px] leading-6 min-h-[24px] max-h-32 overflow-y-auto scrollbar-thin scrollbar-thumb-white/20 scrollbar-track-transparent"
rows="2"></textarea>
</div>
<!-- Toolbar -->
<div class="flex">
<div class="flex-1"></div>
<!-- Send Button -->
<button aria-label="Send message" @click="handleSubmit" :disabled="!inputValue.trim() || loading"
:class="[
'p-2 rounded-xl transition-all duration-200 flex items-center justify-center',
inputValue.trim()
? 'bg-[var(--color-accent)] text-[var(--color-accent-text)] hover:bg-[var(--color-accent-hover)]'
: 'bg-[var(--color-highlight)] text-[var(--color-highlight-high)] cursor-not-allowed'
]">
<Icon v-if="loading" name="svg-spinners:ring-resize" class="w-4 h-4" />
<Icon v-else name="mynaui:send-solid" class="w-4 h-4" />
</button>
</div>
</div>
</div>
</template>
+13
View File
@@ -0,0 +1,13 @@
<script setup lang="ts">
const { hasTasks } = useTasks()
const { open } = useSettings()
</script>
<template>
<ClientOnly>
<Teleport :to="open ? '#settings-loader-target' : '#primary-loader-target'">
<Icon name="svg-spinners:ring-resize"
:class="['text-[var(--color-accent)] text-4', hasTasks ? 'opacity-100' : 'opacity-0']" />
</Teleport>
</ClientOnly>
</template>
+79
View File
@@ -0,0 +1,79 @@
<script setup lang="ts">
const { addTask, completeTask } = useTasks()
const { open, currentPage, setPage, close } = useSettings()
const pages = ['page1', 'page2', 'page3']
const simulateTask = () => {
const handle = addTask()
setTimeout(() => {
completeTask(handle)
}, 2000)
}
</script>
<template>
<div v-if="open" class="fixed inset-0 z-[9999] flex items-center justify-center p-4 bg-black/80"
@click.self="close">
<div class="w-[70vw] max-w-6xl h-[70vh] bg-[var(--color-base)] rounded-xl shadow-2xl border border-[var(--color-highlight)]
overflow-hidden flex max-h-[90vh] p-2">
<!-- Sidebar Nav -->
<nav class="w-64 flex flex-col gap-2">
<div class="flex items-center justify-between pb-4">
<div class="flex items-center gap-2 px-2">
<Icon name="mynaui:cog-four" class="w-7 h-7 text-[var(--color-subtle)] mt-1" />
<h1 class="font-semibold text-center">Settings</h1>
</div>
</div>
<div v-for="page in pages" :key="page" :class="[
'select-none cursor-pointer flex items-center gap-2 px-3 py-2 rounded-lg text-sm transition-colors w-full text-left',
currentPage === page ? 'bg-[var(--color-highlight)]' : 'hover:bg-[var(--color-highlight)]/10'
]" @click="setPage(page)">
{{ page.charAt(0).toUpperCase() + page.slice(1) }}
</div>
</nav>
<!-- Content -->
<main class="flex-1 flex flex-col overflow-hidden">
<header class="flex items-center justify-between pl-2 pb-2">
<h2 class="text-lg font-semibold">{{ currentPage.charAt(0).toUpperCase() + currentPage.slice(1) }}
</h2>
<div class="flex items-center gap-3">
<div id="settings-loader-target"></div>
<button @click="close"
class="p-1.5 rounded-lg text-[var(--color-text)] bg-transparent hover:bg-[var(--color-highlight)]/10 transition-colors">
<Icon name="mynaui:x-solid" class="w-5 h-5" />
</button>
</div>
</header>
<div
class="flex-1 p-6 ml-1 mt-1 bg-[var(--color-neutral)] overflow-y-auto border rounded-lg border-[var(--color-highlight)]">
<div v-if="currentPage === 'page1'">
<p class="text-sm text-[var(--color-subtle)] mb-4">Settings page 1 content. Try adding a task to
the queue
below.
</p>
<div class="flex gap-2">
<button class="accent" @click="simulateTask">
Simulate Task (2s)
</button>
</div>
</div>
<div v-else-if="currentPage === 'page2'">
<p class="text-sm text-[var(--color-subtle)] mb-4">Settings page 2 content with some details.
</p>
<div class="grid grid-cols-2 gap-4 mt-4">
<div class="p-4 bg-[var(--color-highlight-low)] rounded-lg text-sm">Item 1</div>
<div class="p-4 bg-[var(--color-highlight-low)] rounded-lg text-sm">Item 2</div>
</div>
</div>
<div v-else-if="currentPage === 'page3'">
<p class="text-sm text-[var(--color-subtle)] mb-4">Settings page 3 content.</p>
<button class="accent" @click="simulateTask">Run Task</button>
</div>
</div>
</main>
</div>
</div>
</template>
+51
View File
@@ -0,0 +1,51 @@
<script setup lang="ts">
const { user, signOut } = useAuth()
const { toggle: toggleSettings } = useSettings()
const profileRef = ref<HTMLElement | null>(null)
const profileOpen = ref(false)
const hovering = defineModel<boolean>({ required: true })
const toggleProfile = () => {
profileOpen.value = !profileOpen.value
}
const handleLogout = async () => {
await signOut()
profileOpen.value = false
}
useClickOutside(profileRef, () => {
profileOpen.value = false
})
</script>
<template>
<header class="flex items-center justify-between overflow-hidden">
<div role="button" aria-label="open user dropdown" ref="profileRef"
class="flex items-center gap-1.5 pr-2 rounded-xl hover:bg-[var(--color-highlight)] cursor-pointer transition-colors max-w-full"
@click="toggleProfile">
<div
:class="['w-[28px] h-[28px] flex-shrink-0 rounded-lg overflow-hidden bg-[var(--color-neutral)] flex items-center justify-center', user?.image ? '' : 'border border-[var(--color-highlight-high)]']">
<img v-if="user?.image" :src="user.image" class="w-full h-full object-cover" />
<Icon v-else name="mynaui:user" class="w-4 h-4 text-[var(--color-subtle)]" />
</div>
<span
class="text-sm font-medium text-ellipsis overflow-hidden text-[var(--color-text)] whitespace-nowrap">{{
user!.name
}}</span>
<div :class="['flex-shrink-0 w-4 h-4 text-[var(--color-subtle)] transition-all duration-200 ease-[cubic-bezier(0.5,_1,_0.89,_1)] overflow-hidden transform-origin-center-left',
hovering ? 'opacity-100 scale-100' : 'opacity-0 scale-x-0 scale-y-90'
]">
<Icon class="text-4" name="mynaui:chevron-down" />
</div>
</div>
<!-- Profile Dropdown -->
<div v-show="profileOpen"
class="absolute top-full left-0 right-0 z-50 mt-1.5 bg-[var(--color-neutral)] border border-[var(--color-highlight)] rounded-xl p-2 w-full gap-1 flex flex-col text-[var(--color-text)]">
<SidenavItem @click="toggleSettings(); profileOpen = false" name="Settings" icon="mynaui:cog-four" />
<SidenavItem @click="handleLogout" name="Log out" icon="mynaui:logout" />
</div>
</header>
</template>
+75
View File
@@ -0,0 +1,75 @@
<script setup lang="ts">
const { user } = useAuth()
const { agents, activeAgent } = await useAgents()
const homeButtonRef = ref<HTMLElement | null>(null)
const agentDropdownRef = ref<HTMLElement | null>(null)
const agentDropdownOpen = ref(false)
const hovering = defineModel<boolean>({ required: true })
const initialized = ref(false)
onMounted(() => {
if (hovering.value) {
const width = homeButtonRef.value!.scrollWidth
homeButtonRef.value!.style.width = `calc(${width}px + 0.5rem)`
}
watch(hovering, (value) => {
if (!initialized.value) {
initialized.value = true
}
if (value) {
const width = homeButtonRef.value!.scrollWidth
homeButtonRef.value!.style.width = `calc(${width}px + 0.5rem)`
} else {
homeButtonRef.value!.style.width = '0'
}
})
});
useClickOutside(agentDropdownRef, () => {
agentDropdownOpen.value = false
})
</script>
<template>
<header class="flex items-center rounded-lg overflow-hidden">
<div ref="homeButtonRef" style="width: 0;"
:class="['flex flex-shrink-0 items-center overflow-hidden', initialized ? 'transition-all duration-150 ease-[cubic-bezier(0.5,_1,_0.89,_1)]' : '', hovering ? 'opacity-100 scale-100' : 'opacity-0 scale-95']">
<NuxtLink to="/"
class="flex hover:bg-[var(--color-highlight)] rounded-lg decoration-none transition-inherit text-[var(--color-subtle)] p-1.5">
<Icon name="mynaui:chevron-left" class="w-4.5 h-4.5" />
</NuxtLink>
</div>
<div class="flex overflow-hidden gap-1.5 pr-2 items-center cursor-pointer hover:bg-[var(--color-highlight)] rounded-lg"
ref="agentDropdownRef" @click="agentDropdownOpen = !agentDropdownOpen">
<div
:class="['w-[28px] h-[28px] flex-shrink-0 rounded-lg overflow-hidden bg-[var(--color-neutral)] flex items-center justify-center', user?.image ? '' : 'border border-[var(--color-highlight-high)]']">
<img v-if="activeAgent?.imageUrl" :src="activeAgent.imageUrl" class="w-full h-full object-cover" />
<Icon v-else name="mynaui:check-hexagon" class="w-4 h-4 text-[var(--color-accent)]" />
</div>
<span class="text-sm font-medium text-ellipsis overflow-hidden text-[var(--color-text)] whitespace-nowrap">
{{ activeAgent?.name }}
</span>
<div class="w-4 h-4 text-[var(--color-subtle)]">
<Icon
class="text-4 transform-origin-center-left duration-150 ease-[cubic-bezier(0.5,_1,_0.89,_1)] transition-all"
name="mynaui:chevron-up-down" />
</div>
</div>
<!-- Agent Dropdown -->
<div v-show="agentDropdownOpen" class="absolute top-full left-1/10 z-50 mt-1.5 bg-[var(--color-neutral)] border border-[var(--color-highlight)]
rounded-xl p-2 w-8/10">
<div class="flex flex-col gap-1.5 max-h-[calc(2.25rem*4+0.375rem*3)] overflow-y-auto">
<NuxtLink v-for="agent in agents" :to="`/agent/${agent.id}`" :key="agent.id" :class="['decoration-none whitespace-nowrap', activeAgent?.id === agent.id ? 'text-[var(--color-text)]' :
'text-[var(--color-subtle)]']" @click="agentDropdownOpen = false">
<SidenavItem :name="agent.name" icon="mynaui:check-hexagon"
:active="activeAgent?.id === agent.id" />
</NuxtLink>
</div>
</div>
</header>
</template>
+17
View File
@@ -0,0 +1,17 @@
<script setup lang="ts">
const props = defineProps<{
name: string,
icon: string,
active?: boolean
}>()
</script>
<template>
<div role="button"
:class="['flex items-center gap-2 px-1 rounded-lg hover:bg-[var(--color-highlight)] transition-colors cursor-pointer h-9 overflow-hidden', props.active ? 'bg-[var(--color-highlight)]' : '']">
<div class="h-7 w-7 flex items-center justify-center">
<Icon class="text-4.5" :name="props.icon" />
</div>
<span class="text-sm font-medium overflow-hidden text-ellipsis">{{ props.name }}</span>
</div>
</template>
+32
View File
@@ -0,0 +1,32 @@
<script setup lang="ts">
const route = useRoute()
const routeParts = computed(() => {
return route.path.replace('/agent/', '').split('/')
});
if (routeParts.value.length < 1) navigateTo('/')
if (!routeParts.value[0]!.match(/^agents_[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$/)) navigateTo('/')
const pageInfo = computed(() => {
// `/agent/agent_[uuid]`
if (routeParts.value.length === 1) {
return 'new-conversation'
}
// `/agent/agent_[uuid]/profile`
if (routeParts.value.length === 2 && routeParts.value[1]! === 'profile') {
return 'agent-profile'
}
})
</script>
<template>
<nav class="flex flex-col gap-1">
<div class="mt-2">
<NuxtLink :to="`/agent/${routeParts[0]}/profile`" class="decoration-none text-[var(--color-subtle)]">
<SidenavItem name="Agent Info" icon="mynaui:info-square" :active="pageInfo === 'agent-profile'" />
</NuxtLink>
</div>
</nav>
</template>
+102
View File
@@ -0,0 +1,102 @@
<script setup lang="ts">
const { agents, createAgent } = await useAgents()
const route = useRoute()
const agentsListRef = ref<HTMLElement | null>(null)
const agentsOpen = ref(true)
const agentsListHeight = ref('auto')
const agentsListOpacity = ref(1)
const agentsListScale = ref(1)
const creatingAgent = ref(false)
function easeInOutQuad(x: number): number {
return x < 0.5 ? 2 * x * x : 1 - Math.pow(-2 * x + 2, 2) / 2;
}
const toggleAgentsList = () => {
if (!agentsListRef.value) return;
let animationLength = 200;
let animationStart: number | null = null;
let startHeight: number;
let startOpacity = agentsListOpacity.value;
let startScale = agentsListScale.value;
if (agentsListHeight.value === 'auto') {
startHeight = agentsListRef.value.clientHeight;
} else {
startHeight = Number(agentsListHeight.value.replace('px', ''));
}
let targetHeight = agentsOpen.value ? 0 : agentsListRef.value.scrollHeight;
let targetOpacity = agentsOpen.value ? 0 : 1;
let targetScale = agentsOpen.value ? 0.95 : 1;
agentsOpen.value = !agentsOpen.value;
const animate = (timestamp: number) => {
if (!animationStart) animationStart = timestamp;
const elapsed = timestamp - animationStart;
const progress = Math.min(elapsed / animationLength, 1);
const currentHeight = startHeight + (targetHeight - startHeight) * easeInOutQuad(progress);
const currentOpacity = startOpacity + (targetOpacity - startOpacity) * easeInOutQuad(progress);
const currentScale = startScale + (targetScale - startScale) * easeInOutQuad(progress);
agentsListOpacity.value = currentOpacity;
agentsListScale.value = currentScale;
agentsListHeight.value = `${currentHeight}px`;
if (progress < 1) {
requestAnimationFrame(animate);
} else {
if (agentsOpen.value) {
agentsListHeight.value = 'auto';
}
}
};
requestAnimationFrame(animate);
}
const newAgent = async () => {
creatingAgent.value = true;
const agent = await createAgent();
creatingAgent.value = false;
navigateTo(`/agent/${agent!.id}`);
}
</script>
<template>
<nav class="flex flex-col gap-1">
<SidenavItem name="Search" icon="mynaui:search" />
<NuxtLink to="/" class="decoration-none text-[var(--color-text)]">
<SidenavItem class="bg-[var(--color-highlight)]" name="Home" icon="mynaui:home" />
</NuxtLink>
<!-- Agents Section -->
<div class="relative group">
<div class="flex items-center justify-between px-2 h-9 rounded-lg hover:bg-[var(--color-highlight)] transition-colors cursor-pointer"
@click="toggleAgentsList()">
<div class="flex items-center gap-0.5">
<span class="text-sm">Agents</span>
<Icon name="mynaui:chevron-right-solid"
:class="['transition-transform duration-200 ease-in-out transform-origin-center', agentsOpen ? 'rotate-90' : '']" />
</div>
<button aria-label="create new agent"
class="opacity-0 group-hover:opacity-100 p-1 rounded-md transition-all bg-transparent hover:bg-[var(--color-highlight)] text-[var(--color-subtle)] active:text-[var(--color-text)]"
@click.stop="newAgent">
<Icon v-if="!creatingAgent" name="mynaui:plus" class="w-4 h-4" />
<Icon v-else name="svg-spinners:ring-resize" class="w-4 h-4" />
</button>
</div>
<div ref="agentsListRef"
:style="{ height: agentsListHeight, opacity: agentsListOpacity, transform: `scale(${agentsListScale})` }"
class="mt-1 gap-1 flex flex-col overflow-hidden transform-origin-center-top">
<NuxtLink v-for="agent in agents" :to="`/agent/${agent.id}`" :key="agent.id"
class="decoration-none text-[var(--color-subtle)]">
<SidenavItem :name="agent.name" icon="mynaui:check-hexagon" />
</NuxtLink>
</div>
</div>
</nav>
</template>
+123
View File
@@ -0,0 +1,123 @@
<script setup lang="ts">
const { close: closeSidebar, open, sidebarWidth, resize, saveWidth } = useSidebar()
const route = useRoute()
const isResizing = ref(false)
const startX = ref(0)
const initialWidth = ref(0)
const closeSidenavRef = ref<HTMLElement | null>(null)
const onResizeStart = (event: MouseEvent) => {
isResizing.value = true
startX.value = event.clientX
initialWidth.value = sidebarWidth.value
document.body.style.cursor = 'col-resize'
document.body.style.userSelect = 'none'
}
const onResizeMove = (event: MouseEvent) => {
if (!isResizing.value) return
if (resizeAnimationFrame) return;
resizeAnimationFrame = requestAnimationFrame(() => {
const deltaX = event.clientX - startX.value
const newWidth = initialWidth.value + deltaX
resize(newWidth)
resizeAnimationFrame = null
})
}
const onResizeEnd = () => {
if (!isResizing.value) return
isResizing.value = false
document.body.style.cursor = ''
document.body.style.userSelect = ''
saveWidth()
}
let resizeAnimationFrame: number | null = null
onMounted(() => {
document.addEventListener('mousemove', onResizeMove)
document.addEventListener('mouseup', onResizeEnd)
watch(hovering, (value) => {
if (value) {
const width = closeSidenavRef.value!.scrollWidth
closeSidenavRef.value!.style.width = `${width}px`
} else {
closeSidenavRef.value!.style.width = '0'
}
})
})
onUnmounted(() => {
document.removeEventListener('mousemove', onResizeMove)
document.removeEventListener('mouseup', onResizeEnd)
})
const hovering = ref(false)
const navKind = computed(() => {
if (route.path === '/') return 'home'
if (route.path.startsWith('/agent/')) return 'agent'
return null
})
</script>
<template>
<div class="relative">
<aside :class="[
'h-full max-w-fit bg-[var(--color-base)] overflow-hidden will-change-width text-[var(--color-subtle)] select-none',
open ? 'w-full mr-2' : 'w-0 mr-0',
isResizing ? '' : 'transition-[width,margin] duration-250 ease-[cubic-bezier(0,0.55,0.45,1)]'
]" :style="open ? { width: `${sidebarWidth}px` } : {}" @mouseenter="hovering = true"
@mouseleave="hovering = false">
<div :style="{ minWidth: `${sidebarWidth}px` }" class="flex flex-col h-full justify-between">
<div class="flex flex-col">
<!-- Header -->
<div class="relative flex flex-row gap-2 justify-between items-center pb-1.5">
<SidenavHeader v-if="navKind === 'home'" v-model="hovering" />
<SidenavHeaderAgent v-else-if="navKind === 'agent'" v-model="hovering" />
<div class="flex items-center justify-end text-[var(--color-subtle)] gap-0.5">
<div ref="closeSidenavRef" style="width: 0;"
:class="['flex-shrink-0 overflow-hidden rounded-lg transition-all duration-150 ease-[cubic-bezier(0.5,_1,_0.89,_1)] transform-origin-center-right']">
<button aria-label="close sidebar" @click="closeSidebar" :class="[
'text-5 p-1.5 hover:bg-[var(--color-highlight)] bg-transparent transition-inherit',
]">
<Icon name="mynaui:panel-left-close"
:class="['transition-inherit', hovering ? 'opacity-100 scale-100' : 'opacity-0 scale-95']" />
</button>
</div>
<div v-if="navKind === 'agent'" class="flex-shrink-0 overflow-hidden rounded-lg">
<NuxtLink aria-label="Start a new topic" :to="`/agent/${route.params.id}`" :class="[
'flex text-5 p-1.5 hover:bg-[var(--color-highlight)] bg-transparent text-inherit',
]">
<Icon name="mynaui:book-plus" />
</NuxtLink>
</div>
</div>
</div>
<!-- Main Menu -->
<div class="max-h-full overflow-auto">
<SidenavNavHome v-if="navKind === 'home'" />
<SidenavNavAgent v-else-if="navKind === 'agent'" />
</div>
</div>
<!-- Theme Switcher -->
<div class="flex justify-end">
<ThemeSwitcher />
</div>
</div>
</aside>
<!-- resize handle -->
<div @mousedown="onResizeStart" class="absolute top-0 right-0 bottom-0 p-1 cursor-ew-resize">
</div>
</div>
</template>
+52
View File
@@ -0,0 +1,52 @@
<script setup lang="ts">
type Theme = 'light' | 'dark' | 'system'
const colorMode = useColorMode()
const isOpen = ref(false)
const buttonRef = ref<HTMLElement | null>(null)
const themeOptions: { value: Theme; label: string; icon: string }[] = [
{ value: 'light', label: 'Light', icon: 'mynaui:sun' },
{ value: 'dark', label: 'Dark', icon: 'mynaui:moon' },
{ value: 'system', label: 'System', icon: 'mynaui:desktop' }
]
const currentOption = computed(() =>
themeOptions.find(option => option.value === colorMode.preference) || themeOptions[2]
)
const selectTheme = (newTheme: Theme) => {
colorMode.preference = newTheme
isOpen.value = false
}
const toggleDropdown = () => {
isOpen.value = !isOpen.value
}
useClickOutside(buttonRef, () => {
isOpen.value = false
})
</script>
<template>
<div ref="buttonRef" class="relative">
<button aria-label="Open theme switcher" @click="toggleDropdown"
class="p-2 flex items-center justify-center rounded-lg hover:bg-[var(--color-highlight)] transition-colors group"
:class="isOpen ? 'bg-[var(--color-highlight)]' : 'bg-transparent'">
<Icon :name="currentOption!.icon"
class="text-5 text-[var(--color-subtle)] group-hover:text-[var(--color-text)] transition-colors" />
</button>
<div v-if="isOpen"
class="flex flex-col gap-1 absolute bottom-full mb-2 right-0 bg-[var(--color-neutral)] border border-[var(--color-highlight)] rounded-lg p-1 min-w-[120px] shadow-lg">
<button v-for="option in themeOptions" :key="option.value" @click="selectTheme(option.value)"
class="w-full flex items-center justify-left gap-2 px-3 py-2 rounded-md hover:bg-[var(--color-highlight)] transition-colors"
:class="colorMode.preference === option.value ? 'bg-[var(--color-highlight)] text-[var(--color-text)]' :
'bg-transparent text-[var(--color-subtle)]'">
<Icon :name="option.icon" class="w-4 h-4" />
<span class="text-sm">{{ option.label }}</span>
</button>
</div>
</div>
</template>