This commit is contained in:
2024-08-30 18:15:07 +02:00
parent 8cadaf08a0
commit c77503ed45
17 changed files with 273 additions and 149 deletions

View File

@@ -6,18 +6,21 @@ defineProps<{
dropdownItems: { label: string, icon: string, color: string, click: (category: CategoryType) => void }[]
}>()
defineEmits(['createTab'])
const { canCreateTabInCategory } = await useUserLimit()
</script>
<template>
<div v-if="category" class="flex items-center mb-4" :class="category.nameVisible ? 'justify-between' : 'justify-end'">
<div v-if="category.nameVisible" class="flex items-center gap-2 mb-4" :class="`text-${category.color}-500`">
<UIcon :name="`i-ph:${category.icon}`" size="28" />
<UIcon :name="category.icon" size="28" />
<h1 class="font-bold text-2xl">
{{ category.name }}
</h1>
</div>
<div class="flex gap-4">
<UButton
v-if="canCreateTabInCategory(category.id)"
color="gray"
variant="solid"
label="New tab"

View File

@@ -1,9 +1,9 @@
<script setup lang="ts">
const colorMode = useColorMode()
const { user, loggedIn, clear } = useUserSession()
const isSettingsOpen = ref(false)
const isDark = computed(() => colorMode.preference === 'dark')
const items = [
[{
slot: 'account',
@@ -13,26 +13,28 @@ const items = [
label: 'Home',
icon: 'i-ph:house-line-duotone',
action: () => navigateTo('/'),
shortcut: 'H',
}, {
label: 'Settings',
icon: 'i-ph:gear-six-duotone',
action: () => {
isSettingsOpen.value = true
},
action: () => isSettingsOpen.value = true,
shortcut: 'S',
}, {
label: 'Profile',
icon: 'i-ph:person-arms-spread-duotone',
action: () => navigateTo('/profile'),
label: isDark.value ? 'Light mode' : 'Dark mode',
icon: isDark.value ? 'i-ph:moon-duotone' : 'i-ph:sun-duotone',
action: () => toggleColorMode(),
shortcut: 'T',
}],
[{
slot: 'logout',
label: 'Sign out',
icon: 'i-ph:sign-out-bold',
shortcut: 'L',
}],
]
function toggleColorMode() {
colorMode.preference = colorMode.preference === 'dark' ? 'light' : 'dark'
colorMode.preference = isDark.value ? 'light' : 'dark'
}
async function logout() {
@@ -43,6 +45,8 @@ async function logout() {
defineShortcuts({
t: () => toggleColorMode(),
s: () => isSettingsOpen.value = !isSettingsOpen.value,
l: async () => await logout(),
})
</script>
@@ -58,7 +62,13 @@ defineShortcuts({
</h1>
<div class="flex items-center gap-2">
<ClientOnly>
<UDropdown v-if="loggedIn" :items="items" mode="hover" :ui="{ item: { disabled: 'cursor-text select-text' } }" :popper="{ placement: 'bottom-start' }">
<UDropdown
v-if="loggedIn"
:items="items"
mode="hover"
:ui="{ item: { disabled: 'cursor-text select-text' } }"
:popper="{ placement: 'bottom-end' }"
>
<UAvatar :src="user.avatar" />
<template #account>
@@ -74,26 +84,28 @@ defineShortcuts({
<template #item="{ item }">
<div class="w-full flex justify-between items-center" @click.prevent="item.action()">
<span class="truncate">{{ item.label }}</span>
<UIcon :name="item.icon" class="flex-shrink-0 h-4 w-4 text-gray-400 dark:text-gray-500 ms-auto" />
<div class="gap-2 flex items-center">
<UIcon :name="item.icon" class="flex-shrink-0 h-4 w-4 text-gray-400 dark:text-gray-500 ms-auto" />
<span class="truncate">{{ item.label }}</span>
</div>
<UKbd v-if="item.shortcut">
{{ item.shortcut }}
</UKbd>
</div>
</template>
<template #logout="{ item }">
<div class="w-full flex justify-between items-center" @click="logout()">
<span class="truncate">{{ item.label }}</span>
<UIcon :name="item.icon" class="flex-shrink-0 h-4 w-4 text-gray-400 dark:text-gray-500 ms-auto" />
<div class="flex gap-2 items-center">
<UIcon :name="item.icon" class="flex-shrink-0 h-4 w-4 text-gray-400 dark:text-gray-500" />
<span class="truncate">{{ item.label }}</span>
</div>
<UKbd v-if="item.shortcut">
{{ item.shortcut }}
</UKbd>
</div>
</template>
</UDropdown>
<UButton
:icon="$colorMode.preference === 'dark' ? 'i-ph:moon-duotone' : 'i-ph:sun-duotone'"
color="gray"
square
size="md"
variant="ghost"
@click="toggleColorMode"
/>
</clientonly>
</div>
</div>
@@ -111,10 +123,12 @@ defineShortcuts({
</template>
<template #default>
Hey
Delete account
Change user details
{{ user }}
<div class="space-y-12">
<div>
Delete account
Change user details
</div>
</div>
</template>
</UCard>
</USlideover>

View File

@@ -5,8 +5,16 @@ defineProps<{
tab: TabType
}>()
const { setTabPrimary } = await useTabs()
// DropDown Items
const items = [[
{
label: 'Toggle favorite',
icon: 'i-ph:star-duotone',
color: 'yellow',
click: tab => setTabPrimary(tab, !tab.primary),
},
{
label: 'Edit',
icon: 'i-ph:pencil-duotone',
@@ -56,12 +64,12 @@ function openDeleteTabModal(tab: TabType) {
<div class="flex items-center justify-between h-full">
<div class="flex gap-4 items-center h-full">
<UBadge :color="tab.color" class="p-2" variant="soft">
<UIcon :name="`i-ph:${tab.icon}`" size="32" />
<UIcon :name="tab.icon" size="32" />
</UBadge>
<div class="flex flex-col gap-1">
<div :class="`text-${tab.color}-400`" class="text-xl font-medium">
<p>{{ tab.name }}</p>
</div>
<p :class="`text-${tab.color}-400`" class="text-xl font-medium truncate">
{{ tab.name }}
</p>
</div>
</div>
<UDropdown
@@ -71,8 +79,10 @@ function openDeleteTabModal(tab: TabType) {
>
<UButton
color="gray"
variant="soft"
icon="i-ph:dots-three-outline-vertical-duotone"
variant="ghost"
:padded="false"
size="sm"
icon="i-ph:dots-three-outline-duotone"
/>
<template #item="{ item }">

View File

@@ -20,6 +20,8 @@ async function handleCreate(event: FormSubmitEvent<CreateCategorySchemaType>) {
state.icon = undefined
state.name = undefined
}
const { loading, search } = useIcons()
</script>
<template>
@@ -42,11 +44,35 @@ async function handleCreate(event: FormSubmitEvent<CreateCategorySchemaType>) {
<template #default>
<UForm :schema="CreateCategorySchema" :state="state" class="space-y-4" @submit="handleCreate">
<UFormGroup label="Name" name="name">
<UInput v-model="state.name" type="text" variant="outline" />
<UInput v-model="state.name" placeholder="Enter name" type="text" variant="outline" />
</UFormGroup>
<UFormGroup label="Icon " name="icon" help="Get icon from the Phosphor Collection">
<UInput v-model="state.icon" type="text" variant="outline" />
<UFormGroup label="Icon " name="icon">
<USelectMenu
v-model="state.icon"
:loading="loading"
:searchable="search"
placeholder="Select an icon"
searchable-placeholder="Search an icon"
>
<template #label>
<div v-if="state.icon" class="flex items-center gap-2">
<UIcon size="20" :name="`i-${state.icon}`" />
<span class="truncate">{{ state.icon }}</span>
</div>
</template>
<template #option="{ option }">
<div class="flex items-center gap-2">
<UIcon size="20" :name="`i-${option}`" />
<span class="truncate">{{ option }}</span>
</div>
</template>
<template #empty>
Enter an icon name, keyword or tag
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup label="Color " name="color">

View File

@@ -33,6 +33,8 @@ async function handleCreate(event: FormSubmitEvent<CreateTabSchemaType>) {
state.primary = false
state.categoryId = props.category?.id
}
const { loading, search } = useIcons()
</script>
<template>
@@ -59,7 +61,31 @@ async function handleCreate(event: FormSubmitEvent<CreateTabSchemaType>) {
</UFormGroup>
<UFormGroup label="Icon " name="icon">
<UInput v-model="state.icon" type="text" />
<USelectMenu
v-model="state.icon"
:loading="loading"
:searchable="search"
placeholder="Select an icon"
searchable-placeholder="Search an icon"
>
<template #label>
<div v-if="state.icon" class="flex items-center gap-2">
<UIcon size="20" :name="`i-${state.icon}`" />
<span class="truncate">{{ state.icon }}</span>
</div>
</template>
<template #option="{ option }">
<div class="flex items-center gap-2">
<UIcon size="20" :name="`i-${option}`" />
<span class="truncate">{{ option }}</span>
</div>
</template>
<template #empty>
Enter an icon name, keyword or tag
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup label="Color " name="color">

View File

@@ -8,12 +8,13 @@ const props = defineProps<{
const emit = defineEmits(['closeModal'])
const { updateCategory } = await useCategories()
const { loading, search } = useIcons()
const state = reactive({
name: props.category?.name,
icon: props.category?.icon,
color: props.category?.color,
nameVisible: props.category?.nameVisible,
name: undefined,
icon: undefined,
color: COLORS[0],
nameVisible: undefined,
})
watchEffect(() => {
@@ -60,9 +61,32 @@ async function handleUpdate(event: FormSubmitEvent<UpdateCategorySchema>) {
</UFormGroup>
<UFormGroup label="Icon " name="icon">
<UInput v-model="state.icon" type="text" />
</UFormGroup>
<USelectMenu
v-model="state.icon"
:loading="loading"
:searchable="search"
placeholder="Select an icon"
searchable-placeholder="Search an icon"
>
<template #label>
<div v-if="state.icon" class="flex items-center gap-2">
<UIcon size="20" :name="`i-${state.icon}`" />
<span class="truncate">{{ state.icon }}</span>
</div>
</template>
<template #option="{ option }">
<div class="flex items-center gap-2">
<UIcon size="20" :name="`i-${option}`" />
<span class="truncate">{{ option }}</span>
</div>
</template>
<template #empty>
Enter an icon name, keyword or tag
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup>
<UCheckbox v-model="state.nameVisible" :color="state.color" label="Is the category name visible?" />
</UFormGroup>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import type { FormSubmitEvent } from '#ui/types'
import type { COLORS, TabType, UpdateTabSchemaType } from '~~/types/types'
import { UpdateTabSchema } from '~~/types/types'
import type { TabType, UpdateTabSchemaType } from '~~/types/types'
import { COLORS, UpdateTabSchema } from '~~/types/types'
const props = defineProps<{
tab: TabType | null
@@ -10,13 +10,14 @@ const props = defineProps<{
const emit = defineEmits(['closeModal'])
const { categories } = await useCategories()
const { updateTab } = await useTabs()
const { loading, search } = useIcons()
const state = reactive({
name: props.tab?.name,
icon: props.tab?.icon,
color: props.tab?.color,
primary: props.tab?.primary,
categoryId: props.tab?.categoryId,
name: undefined,
icon: undefined,
color: COLORS[0],
primary: undefined,
categoryId: undefined,
})
watchEffect(() => {
@@ -65,7 +66,31 @@ async function handleUpdate(event: FormSubmitEvent<UpdateTabSchemaType>) {
</UFormGroup>
<UFormGroup label="Icon " name="icon">
<UInput v-model="state.icon" type="text" />
<USelectMenu
v-model="state.icon"
:loading="loading"
:searchable="search"
placeholder="Select an icon"
searchable-placeholder="Search an icon"
>
<template #label>
<div v-if="state.icon" class="flex items-center gap-2">
<UIcon size="20" :name="`i-${state.icon}`" />
<span class="truncate">{{ state.icon }}</span>
</div>
</template>
<template #option="{ option }">
<div class="flex items-center gap-2">
<UIcon size="20" :name="`i-${option}`" />
<span class="truncate">{{ option }}</span>
</div>
</template>
<template #empty>
Enter an icon name, keyword or tag
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup label="Category " name="category">

View File

@@ -1,55 +0,0 @@
<script setup lang="ts">
import type { AppType } from '~~/types/types'
defineProps<{
title: string
apps: AppType[]
}>()
</script>
<template>
<section>
<h1 class="font-bold text-xl mb-4">
{{ title }}
</h1>
<div v-if="apps" class="grid grid-cols-1 auto-rows-auto sm:grid-cols-3 md:grid-cols-5 gap-4">
<ULink v-for="app in apps" :key="app.name" :to="app.url" class="relative" target="_blank">
<div v-show="app.primary === true" class="absolute flex h-4 w-4 -right-2 -top-2">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" />
<span class="relative inline-flex rounded-full h-4 w-4 bg-green-500" />
</div>
<UCard
:ui="{ body: { base: 'h-full' }, background: 'h-full duration-300 bg-white hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-800' }"
>
<div class="flex gap-4 items-center h-full">
<UBadge :color="app.color" class="p-2" variant="soft">
<UIcon :name="app.icon" size="32" />
</UBadge>
<div class="flex flex-col gap-1">
<div v-if="app.nuxt" class="text-xl flex gap-1 items-center">
<div class="flex">
<p>{{ app.name }}</p>
<p :class="`text-${app.color}-400 font-medium`">
{{ app.nuxt }}
</p>
</div>
</div>
<div v-else :class="`text-${app.color}-400`" class="text-xl font-medium">
<p>{{ app.name }}</p>
</div>
<div class="flex gap-2 mt-1">
<UBadge
v-for="tag in app.tags"
:key="tag.name"
:color="tag.color"
:label="tag.name"
variant="soft"
/>
</div>
</div>
</div>
</UCard>
</ULink>
</div>
</section>
</template>