Added category creation interface

This commit is contained in:
Alexis
2025-04-15 15:34:05 +02:00
parent c25c10f7b7
commit 58f3783b13
12 changed files with 285 additions and 32 deletions

View File

@@ -11,15 +11,7 @@ const sortedCategories = computed(() => categories.toSorted((a, b) => a.name.loc
<template>
<div class="flex flex-col h-full">
<header class="grid grid-cols-12 gap-4 px-2 py-4 border-b-[1px] border-b-foreground/10 font-bold">
<div class="col-span-6">
Nom
</div>
<div class="col-span-4">
Couleur
</div>
<div class="col-span-2" />
</header>
<CalendarCategoryTableHeader />
<ScrollAreaRoot class="h-36 grow overflow-hidden ">
<ScrollAreaViewport class="w-full h-full pr-4" as-child>
@@ -40,5 +32,7 @@ const sortedCategories = computed(() => categories.toSorted((a, b) => a.name.loc
/>
</ScrollAreaScrollbar>
</ScrollAreaRoot>
<CalendarCategoryTableFooter />
</div>
</template>

View File

@@ -0,0 +1,130 @@
<script lang="ts" setup>
import { PhPlus } from "@phosphor-icons/vue"
import { useToast } from "~/components/ui/toast"
import { ToastLifetime } from "~/components/ui/toast/use-toast"
import { cn } from "~/lib/utils"
const { toast } = useToast()
const { t } = useI18n()
type FooterMode = "add" | "view"
const currentMode = ref<FooterMode>("view")
const rowRef = ref<HTMLDivElement | null>(null)
const inputRef = ref<HTMLInputElement | null>(null)
const { focused: inputFocused } = useFocus(inputRef)
/**
* Toggle view mode options
*/
type ToggleViewOptions = {
execution?: "now" | "nextTick"
}
/**
* Toggle view mode
* @param options.execution - When to execute the toggle. "now" or "nextTick"
*/
function toggleView(options: ToggleViewOptions = { execution: "now" }) {
currentMode.value = "view"
if (options.execution === "now") {
inputFocused.value = false
} else {
nextTick(() => {
inputFocused.value = false
})
}
}
/**
* Toggle add mode
*/
function toggleAdd() {
currentMode.value = "add"
categorySkeleton.value = { name: "", color: "black" }
nextTick(() => {
inputFocused.value = true
})
}
onClickOutside(rowRef, () => toggleView({ execution: "nextTick" }))
onKeyStroke("Escape", () => toggleView({ execution: "now" }))
const { addCategoryFromSkeleton } = useCategoryStore()
const { categorySkeleton } = storeToRefs(useCategoryStore())
/**
* Submit the update
*/
async function submitNew() {
if (!categorySkeleton.value) return
const newCategoryName = toRaw(categorySkeleton.value).name
const { error } = await tryCatch(addCategoryFromSkeleton())
if (error) {
toast({
title: t("entity.category.addedToast.titleError", { category: newCategoryName }),
variant: "destructive",
duration: ToastLifetime.LONG
})
return
}
toggleView({ execution: "now" })
toast({
title: t("entity.category.addedToast.title", { category: newCategoryName }),
variant: "success",
duration: ToastLifetime.SHORT
})
}
</script>
<template>
<div class="border-y-[1px] border-b-foreground/10 mr-4">
<form
ref="rowRef"
class="grid grid-cols-12 items-center gap-4 p-1 bg-transparent hover:bg-slate-50 dark:bg-transparent dark:hover:bg-slate-900"
@submit.prevent="submitNew"
>
<div v-if="currentMode === 'add'" class="col-span-1" />
<div
:class="cn({
'col-span-6': currentMode === 'view',
'col-span-5': currentMode === 'add'
})"
>
<template v-if="currentMode === 'view'">
<button
class="p-2 h-full w-full text-left underline-offset-4 hover:underline cursor-pointer"
@click="toggleAdd"
>
<PhPlus size="18" />
</button>
</template>
<template v-if="currentMode === 'add' && categorySkeleton">
<input
ref="inputRef"
v-model="categorySkeleton.name"
type="text"
class="p-1 h-full w-full bg-transparent focus-visible:outline-none italic"
>
</template>
</div>
<div class="col-span-4">
<template v-if="currentMode === 'add' && categorySkeleton">
<div class="-mx-2">
<InputColor
id="category-color"
v-model="categorySkeleton.color"
position="item-aligned"
theme="subtle"
/>
</div>
</template>
</div>
</form>
</div>
</template>

View File

@@ -0,0 +1,19 @@
<script setup lang="ts">
import { PhListBullets } from "@phosphor-icons/vue";
</script>
<template>
<header class="grid grid-cols-12 gap-4 px-2 py-4 border-b-[1px] border-b-foreground/10 font-bold mr-4">
<div class="col-span-1 flex items-end">
<PhListBullets size="20" />
</div>
<div class="col-span-5">
Nom
</div>
<div class="col-span-4">
Couleur
</div>
<div class="col-span-2" />
</header>
</template>

View File

@@ -34,10 +34,10 @@ function toggleView(options: ToggleViewOptions = { execution: "now" }) {
currentMode.value = "view"
if (options.execution === "now") {
inputFocused.value = false;
inputFocused.value = false
} else {
nextTick(() => {
inputFocused.value = false;
inputFocused.value = false
})
}
}
@@ -46,11 +46,11 @@ function toggleView(options: ToggleViewOptions = { execution: "now" }) {
* Toggle edit mode
*/
function toggleEdit() {
currentMode.value = "edit";
currentMode.value = "edit"
categorySkeleton.value = structuredClone(toRaw(category))
nextTick(() => {
inputFocused.value = true;
inputFocused.value = true
})
}
@@ -66,7 +66,6 @@ onUnmounted(() => {
/**
* Submit the update
* TODO: Implement the update logic
*/
async function submitUpdate() {
const { error } = await tryCatch(updateCategoryFromSkeleton())
@@ -90,18 +89,20 @@ async function submitUpdate() {
</script>
<template>
<div class="relative">
<div ref="rowRef" class="relative">
<form
ref="rowRef"
class="grid grid-cols-12 items-center gap-4 p-1"
class="grid grid-cols-12 items-center gap-4 p-1 border-b-[1px] border-b-foreground/10"
:class="cn(
{ 'bg-slate-100 hover:bg-slate-200 dark:bg-slate-900 dark:hover:bg-slate-800': currentMode === 'edit' },
{ 'bg-transparent hover:bg-slate-50 dark:bg-transparent dark:hover:bg-slate-900': currentMode !== 'edit' }
)"
@submit.prevent="submitUpdate"
>
<div class="col-span-1 pointer-events-none">
<span class="opacity-50 p-2">{{ category.id }}</span>
</div>
<div
class="col-span-6"
class="col-span-5"
>
<template v-if="currentMode === 'view'">
<button
@@ -134,7 +135,7 @@ async function submitUpdate() {
</span>
</button>
</template>
<template v-else-if="currentMode === 'edit' && categorySkeleton">
<template v-else-if="currentMode === 'edit' && categorySkeleton">
<div class="-mx-2">
<InputColor
id="category-color"

View File

@@ -13,7 +13,7 @@ const model = defineModel<RPGColor>({ default: "white" });
<template>
<UiSelect v-model="model">
<UiSelectTrigger :id :class="cn({ 'h-auto': theme === 'subtle' })">
<UiSelectTrigger :id :class="cn({ 'h-9': theme === 'subtle' })">
<UiSelectValue
:placeholder="$t('ui.colors.selectOne')"
class="bgc"