8 Commits

Author SHA1 Message Date
Alexis
7443769f00 Updated package 2025-10-06 21:37:31 +02:00
Alexis
692aaad868 Updated package 2025-09-12 19:52:32 +02:00
Alexis
371bb7e38a Updated package 2025-09-03 21:14:07 +02:00
Alexis
c4e70eb29c Changed quick navigation (arrows will be for days) 2025-08-24 19:53:43 +02:00
Alexis
f8d2387b59 Added quick navigation for calendar pages 2025-08-24 19:44:27 +02:00
Alexis
8c53616c79 Added arrow navigation on calendar page 2025-08-24 15:02:42 +02:00
Alexis
8dff0763e9 Changed calendar menu subnav 2025-08-24 13:47:03 +02:00
Alexis
9c7b503ca8 Merge branch 'feat/nav' into dev 2025-08-24 11:49:21 +02:00
9 changed files with 2863 additions and 3554 deletions

View File

@@ -1,6 +1,5 @@
<script lang="ts" setup>
import { useCalendar } from "@/stores/CalendarStore"
import { computed, type Component, type ComputedRef } from "vue"
// import { PhMagnifyingGlass } from '@phosphor-icons/vue'
import MonthlyLayout from "./state/monthly/Layout.vue"
@@ -8,8 +7,10 @@ import CenturyLayout from "./state/centennially/Layout.vue"
import DecadeLayout from "./state/decennially/Layout.vue"
import YearLayout from "./state/yearly/Layout.vue"
const { currentConfig, jumpToDate, selectedDate } = useCalendar()
const { isReadOnly } = storeToRefs(useCalendar())
import { onKeyDown } from "@vueuse/core"
const { currentConfig, jumpToDate, selectedDate, toPastFar, toPastNear, toFutureNear, toFutureFar, toggleCreatingEventModal } = useCalendar()
const { isReadOnly, calendarState } = storeToRefs(useCalendar())
const currentViewComponent: ComputedRef<Component> = computed<Component>(() => {
switch (currentConfig.viewType) {
@@ -31,6 +32,45 @@ const currentViewComponent: ComputedRef<Component> = computed<Component>(() => {
onMounted(() => {
jumpToDate(selectedDate)
})
/**
* Keyboard quick controls
*/
// Key combos to navigate
const {
home, pageUp,
end, pageDown,
} = useMagicKeys()
watch(home!, (k) => {
if (calendarState.value === 'active' && k) {
toPastFar()
}
})
watch(pageUp!, (k) => {
if (calendarState.value === 'active' && k) {
toPastNear()
}
})
watch(pageDown!, (k) => {
if (calendarState.value === 'active' && k) {
toFutureNear()
}
})
watch(end!, (k) => {
if (calendarState.value === 'active' && k) {
toFutureFar()
}
})
// Key combo to create an event
onKeyDown("+", (e) => {
if (calendarState.value === 'active') {
e.preventDefault()
toggleCreatingEventModal()
}
})
</script>
<template>

View File

@@ -3,20 +3,8 @@ import { breakpointsTailwind, useBreakpoints } from "@vueuse/core"
import { PhPlus } from "@phosphor-icons/vue";
import { VisuallyHidden } from "radix-vue";
const isDialogOpen = ref<boolean>(false);
const { resetSkeleton } = useCalendar();
// Watch the popover state
watch(isDialogOpen, (hasOpened, _o) => {
if (hasOpened) {
resetSkeleton()
}
})
// Toggles the dialog
function toggleDialog() {
isDialogOpen.value = !isDialogOpen.value;
};
const { isCreatingEventModalOpen } = storeToRefs(useCalendar());
const { resetSkeleton, toggleCreatingEventModal } = useCalendar();
/**
* Prevents the modal from closing if's still loading
@@ -36,7 +24,7 @@ const breakpoints = useBreakpoints(
<UiButton
class="max-md:fixed max-md:bottom-8 max-md:right-8 max-md:z-50 max-md:size-14 max-md:rounded-xl"
:size="breakpoints.md.value ? 'default' : 'icon'"
@click="toggleDialog"
@click="toggleCreatingEventModal"
>
<PhPlus :size="breakpoints.md.value ? 18 : 24" weight="bold" />
@@ -47,7 +35,7 @@ const breakpoints = useBreakpoints(
</Transition>
</ClientOnly>
<UiDialog v-model:open="isDialogOpen">
<UiDialog v-model:open="isCreatingEventModalOpen">
<UiDialogContent
class="max-md:translate-0 max-md:inset-0 max-md:w-full max-md:block"
:trap-focus="true"
@@ -64,7 +52,7 @@ const breakpoints = useBreakpoints(
</UiDialogDescription>
</VisuallyHidden>
<CalendarFormCreateEvent @event-created="toggleDialog" />
<CalendarFormCreateEvent @event-created="toggleCreatingEventModal" />
</UiDialogContent>
</UiDialog>
</template>

View File

@@ -12,7 +12,7 @@ interface DirectionLabels {
futureFar: string
}
const { currentConfig, decrementViewMonth, incrementViewMonth, decrementViewYear, incrementViewYear } =
const { currentConfig, toPastFar, toPastNear, toFutureNear, toFutureFar } =
useCalendar()
const activeDirectionLabels: ComputedRef<DirectionLabels> = computed(() => {
@@ -51,101 +51,17 @@ const activeDirectionLabels: ComputedRef<DirectionLabels> = computed(() => {
}
}
})
function toPastFar(): void {
switch (currentConfig.viewType) {
case "month":
decrementViewYear()
break
case "year":
decrementViewYear(10)
break
case "decade":
decrementViewYear(100)
break
case "century":
default:
decrementViewYear(1000)
break
}
}
function toPastNear(): void {
switch (currentConfig.viewType) {
case "month":
decrementViewMonth()
break
case "year":
decrementViewYear()
break
case "decade":
decrementViewYear(10)
break
case "century":
default:
decrementViewYear(100)
break
}
}
function toFutureNear(): void {
switch (currentConfig.viewType) {
case "month":
incrementViewMonth()
break
case "year":
incrementViewYear()
break
case "decade":
incrementViewYear(10)
break
case "century":
default:
incrementViewYear(100)
break
}
}
function toFutureFar(): void {
switch (currentConfig.viewType) {
case "month":
incrementViewYear()
break
case "year":
incrementViewYear(10)
break
case "decade":
incrementViewYear(100)
break
case "century":
default:
incrementViewYear(1000)
break
}
}
</script>
<template>
<div class="flex gap-2">
<div class="grid items-center w-40 px-3 md:px-4 py-2 bg-background border-border border-x-[1px] border-t-[1px] rounded-t-sm text-sm max-md:text-xs transition-colors">
<div class="flex gap-1">
<div class="grid items-center w-40 px-1.5 md:px-3 py-1 bg-background border-border border-x-[1px] border-t-[1px] rounded-t-sm">
<ClientOnly>
<span>{{ currentDate.currentDateTitle }}</span>
<span class="text-xs font-medium">{{ currentDate.currentDateTitle }}</span>
<template #fallback>
<span class="inline-block">
<UiSkeleton class="h-[19px] w-full rounded-sm" />
<UiSkeleton class="h-[18px] w-full rounded-sm" />
</span>
</template>
</ClientOnly>
@@ -157,10 +73,10 @@ function toFutureFar(): void {
<UiButton
variant="outline"
size="icon"
class="rounded-t-sm rounded-b-none border-b-0"
class="size-8 rounded-t-sm rounded-b-none border-b-0"
@click="toPastFar()"
>
<PhCaretDoubleLeft size="18" />
<PhCaretDoubleLeft size="14" />
</UiButton>
</UiTooltipTrigger>
<UiTooltipContent>
@@ -176,10 +92,10 @@ function toFutureFar(): void {
<UiButton
variant="outline"
size="icon"
class="rounded-t-sm rounded-b-none border-b-0"
class="size-8 rounded-t-sm rounded-b-none border-b-0"
@click="toPastNear()"
>
<PhCaretLeft size="18" />
<PhCaretLeft size="14" />
</UiButton>
</UiTooltipTrigger>
<UiTooltipContent>
@@ -195,10 +111,10 @@ function toFutureFar(): void {
<UiButton
variant="outline"
size="icon"
class="rounded-t-sm rounded-b-none border-b-0"
class="size-8 rounded-t-sm rounded-b-none border-b-0"
@click="toFutureNear()"
>
<PhCaretRight size="18" />
<PhCaretRight size="14" />
</UiButton>
</UiTooltipTrigger>
<UiTooltipContent>
@@ -214,10 +130,10 @@ function toFutureFar(): void {
<UiButton
variant="outline"
size="icon"
class="rounded-t-sm rounded-b-none border-b-0"
class="size-8 rounded-t-sm rounded-b-none border-b-0"
@click="toFutureFar()"
>
<PhCaretDoubleRight size="18" />
<PhCaretDoubleRight size="14" />
</UiButton>
</UiTooltipTrigger>
<UiTooltipContent>

View File

@@ -26,7 +26,7 @@ const props = withDefaults(defineProps<Props>(), {
<span
v-if="props.searchSlash"
class="h-4 p-1 ml-1 grid place-items-center text-[0.7em] leading-none font-semibold text-slate-500 bg-slate-100 border-slate-300 border-[1px] rounded-[3px] shadow-xs group-hover:text-slate-600 group-hover:bg-slate-200 group-hover:border-slate-400 transition-colors"
class="h-4 p-1 ml-1 grid place-items-center text-[0.75em] leading-none font-semibold text-slate-500 bg-slate-100 border-slate-300 border-[1px] rounded-[3px] shadow-xs group-hover:text-slate-600 group-hover:bg-slate-200 group-hover:border-slate-400 transition-colors"
>
<kbd class="-mt-[2px]">CTRL + :</kbd>
</span>

View File

@@ -10,6 +10,8 @@ import type { CalendarEvent } from "@@/models/CalendarEvent"
import type { CalendarMonth } from "@@/models/CalendarMonth"
import type { Category } from "@@/models/Category"
type CalendarState = "active" | "idle"
type CalendarViewType = "month" | "year" | "decade" | "century"
type CalendarCurrentConfig = {
@@ -668,6 +670,90 @@ export const useCalendar = defineStore("calendar", () => {
currentEvents.value = computeCurrentEvents()
}, { deep: true, immediate: true })
function toPastFar(): void {
switch (currentConfig.value.viewType) {
case "month":
decrementViewYear()
break
case "year":
decrementViewYear(10)
break
case "decade":
decrementViewYear(100)
break
case "century":
default:
decrementViewYear(1000)
break
}
}
function toPastNear(): void {
switch (currentConfig.value.viewType) {
case "month":
decrementViewMonth()
break
case "year":
decrementViewYear()
break
case "decade":
decrementViewYear(10)
break
case "century":
default:
decrementViewYear(100)
break
}
}
function toFutureNear(): void {
switch (currentConfig.value.viewType) {
case "month":
incrementViewMonth()
break
case "year":
incrementViewYear()
break
case "decade":
incrementViewYear(10)
break
case "century":
default:
incrementViewYear(100)
break
}
}
function toFutureFar(): void {
switch (currentConfig.value.viewType) {
case "month":
incrementViewYear()
break
case "year":
incrementViewYear(10)
break
case "decade":
incrementViewYear(100)
break
case "century":
default:
incrementViewYear(1000)
break
}
}
/**
* Determines if the event can appear in the front end
*
@@ -805,6 +891,22 @@ export const useCalendar = defineStore("calendar", () => {
}
}
/**
* State for event modal creation
*/
const isCreatingEventModalOpen = ref<boolean>(false)
// Watch the popover state
watch(isCreatingEventModalOpen, (hasOpened, _o) => {
if (hasOpened) {
resetSkeleton()
}
})
function toggleCreatingEventModal() {
isCreatingEventModalOpen.value = !isCreatingEventModalOpen.value
}
/**
* State for event modal edition
*/
@@ -936,6 +1038,19 @@ export const useCalendar = defineStore("calendar", () => {
isCategoriesModalOpen.value = state
}
/**
* Handle the state of the active calendar for UI interactions
*/
const calendarState = ref<CalendarState>("active")
watch([isCategoriesModalOpen, isCreatingEventModalOpen, isDeleteEventModalOpen, isEditEventModalOpen, isAdvancedSearchOpen, isDraggingEvent], (values) => {
if (values.every(v => !v)) {
calendarState.value = 'active'
} else {
calendarState.value = 'idle'
}
})
return {
isReadOnly,
setReadStatus,
@@ -975,10 +1090,15 @@ export const useCalendar = defineStore("calendar", () => {
areDatesIdentical,
compareDates,
getRelativeString,
toPastFar,
toPastNear,
toFutureNear,
toFutureFar,
baseEvents,
allEvents,
categories,
currentEvents,
calendarState,
getRelativeEventFromDate,
getRelativeEventFromEvent,
cancelLatestRequest,
@@ -993,6 +1113,8 @@ export const useCalendar = defineStore("calendar", () => {
lastActiveEvent,
updateEventFromSkeleton,
deleteEventFromSkeleton,
isCreatingEventModalOpen,
toggleCreatingEventModal,
isEditEventModalOpen,
revealEditEventModal,
isDeleteEventModalOpen,

View File

@@ -1,3 +1,4 @@
import { defineNuxtConfig } from 'nuxt/config'
import tailwindcss from "@tailwindcss/vite";
// https://nuxt.com/docs/api/configuration/nuxt-config

View File

@@ -13,57 +13,57 @@
"lint:prettier": "prettier . --check"
},
"dependencies": {
"@intlify/message-compiler": "^11.1.11",
"@nuxt/content": "3.6.3",
"@intlify/message-compiler": "^11.1.12",
"@nuxt/content": "3.7.0",
"@nuxt/eslint": "^1.9.0",
"@nuxt/image": "1.11.0",
"@nuxthub/core": "^0.9.0",
"@nuxtjs/i18n": "^10.0.6",
"@nuxtjs/i18n": "^10.1.0",
"@phosphor-icons/vue": "^2.2.1",
"@pinia/nuxt": "^0.11.2",
"@tailwindcss/vite": "^4.1.12",
"@tiptap/pm": "^3.3.0",
"@tiptap/starter-kit": "^3.3.0",
"@tiptap/vue-3": "^3.3.0",
"@vueuse/core": "^13.7.0",
"@vueuse/integrations": "^13.7.0",
"@vueuse/nuxt": "^13.7.0",
"better-sqlite3": "^12.2.0",
"@tailwindcss/vite": "^4.1.14",
"@tiptap/pm": "^3.6.5",
"@tiptap/starter-kit": "^3.6.5",
"@tiptap/vue-3": "^3.6.5",
"@vueuse/core": "^13.9.0",
"@vueuse/integrations": "^13.9.0",
"@vueuse/nuxt": "^13.9.0",
"better-sqlite3": "^12.4.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-vue-next": "^0.541.0",
"luxon": "^3.7.1",
"nuxt": "^4.0.3",
"lucide-vue-next": "^0.544.0",
"luxon": "^3.7.2",
"nuxt": "^4.1.3",
"pinia": "^3.0.3",
"radix-vue": "^1.9.17",
"reka-ui": "^2.4.1",
"shadcn-nuxt": "^2.2.0",
"reka-ui": "^2.5.1",
"shadcn-nuxt": "^2.3.1",
"sortablejs": "^1.15.6",
"tailwind-merge": "^3.3.1",
"tailwindcss-animate": "^1.0.7",
"tw-animate-css": "^1.3.7",
"vue": "^3.5.19",
"tw-animate-css": "^1.4.0",
"vue": "^3.5.22",
"vue-router": "^4.5.1",
"zod": "^4.1.0"
"zod": "^4.1.12"
},
"devDependencies": {
"@nuxtjs/color-mode": "^3.5.2",
"@nuxtjs/supabase": "^1.6.1",
"@nuxtjs/supabase": "^1.6.2",
"@nuxtjs/tailwindcss": "^6.14.0",
"@stylistic/eslint-plugin-js": "^4.4.1",
"@types/luxon": "^3.7.1",
"@typescript-eslint/eslint-plugin": "^8.40.0",
"@typescript-eslint/parser": "^8.40.0",
"eslint": "^9.34.0",
"@typescript-eslint/eslint-plugin": "^8.46.0",
"@typescript-eslint/parser": "^8.46.0",
"eslint": "^9.37.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-vue": "^10.4.0",
"eslint-plugin-vue": "^10.5.0",
"postcss": "^8.5.6",
"prettier": "^3.6.2",
"sass": "^1.90.0",
"supabase": "^2.34.3",
"tailwindcss": "^4.1.12",
"typescript": "^5.9.2",
"wrangler": "^4.32.0"
"sass": "^1.93.2",
"supabase": "^2.48.3",
"tailwindcss": "^4.1.14",
"typescript": "^5.9.3",
"wrangler": "^4.42.0"
}
}

6056
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff