54 Commits

Author SHA1 Message Date
Alexis
6b01ce181d Added back breadcrumb 2025-04-29 16:16:45 +02:00
Alexis
acb5c0d92f Added middleware for closing sidebar on route change 2025-04-29 09:34:58 +02:00
Alexis
387597755d Added sidebar toggle 2025-04-29 09:28:49 +02:00
Alexis
a5f7e1ded9 Added first sidebar draft 2025-04-28 17:18:06 +02:00
Alexis
93a03ca4e2 Removed breadcrumbs on mobile 2025-04-28 12:27:10 +02:00
Alexis
a6fe4c7ea3 Categories appear on more options 2025-04-28 11:29:33 +02:00
Alexis
805972bf3c Fixed css color issue 2025-04-26 21:16:35 +02:00
Alexis
c071e9c33b Added some transitions 2025-04-26 21:01:52 +02:00
Alexis
b430ab2660 Changed head description 2025-04-26 20:32:31 +02:00
Alexis
a6d388f5da Added missing description to event creation dialog 2025-04-26 20:19:11 +02:00
Alexis
a03b7890e6 Made dialog go full screen on mobile 2025-04-25 21:30:40 +02:00
Alexis
b71e715cf5 Fixed call stack issue 2025-04-25 21:06:19 +02:00
Alexis
9ba09c2d2b Fixed black button for day tile 2025-04-25 17:56:56 +02:00
Alexis
b1ff2ec232 Changed today CTA on mobile 2025-04-25 17:53:40 +02:00
Alexis
093b4e7493 Fixed overflow issues on search 2025-04-25 17:36:54 +02:00
Alexis
7b0bb1e939 Made event creation dialog behaviour the same 2025-04-25 17:33:45 +02:00
Alexis
60bbb6f48f Fixed no contrast event buttons 2025-04-25 17:30:50 +02:00
Alexis
f502f80341 Updated list style 2025-04-25 17:25:01 +02:00
Alexis
e634cc8127 Fixed some inconsistencies 2025-04-25 16:56:09 +02:00
Alexis
ee29169f4f Added transitions for client only buttons 2025-04-25 15:38:37 +02:00
Alexis
2d2069cae6 Fixed hydration mismatches 2025-04-25 15:23:03 +02:00
Alexis
f7f12feaec Changed popover to modal on small screens 2025-04-25 14:58:47 +02:00
Alexis
5b8fedba7c Added space-x for alert dialog 2025-04-25 14:17:48 +02:00
Alexis
fa872e6914 Fixed collision padding for calendar events 2025-04-25 14:15:20 +02:00
Alexis
43dd12782b Reduced spacing on calendar menu 2025-04-23 21:59:45 +02:00
Alexis
f1f140f2bc Reduced some spacing on mobile 2025-04-23 21:41:32 +02:00
Alexis
707d4374ef Changed calendar layout for buttons 2025-04-23 16:21:51 +02:00
AlexisNP
048413286f Merge pull request #77 from AlexisNP/upgrade/nuxt
Upgrade/nuxt
2025-04-23 14:11:29 +02:00
Alexis
9d06040c5d Changed muted colour 2025-04-22 22:30:14 +02:00
Alexis
15bfc66087 Removed dark classes for calendar preview 2025-04-22 22:15:49 +02:00
Alexis
e6b2670438 Removed character string 2025-04-22 17:17:13 +02:00
Alexis
e2d2810db6 Event creation form closes if the date changes 2025-04-22 17:07:29 +02:00
Alexis
1827b9118f Changed chip colour for tags input 2025-04-22 15:36:58 +02:00
Alexis
086b997845 Fixed default breadcrumb look 2025-04-22 15:31:41 +02:00
Alexis
73f5e01244 Removed default lucide icons and updated some packages 2025-04-22 14:09:25 +02:00
Alexis
d02bfacd57 Made search bigger 2025-04-18 18:07:13 +02:00
Alexis
5370a9df06 Added color square to event creation forms 2025-04-18 18:03:55 +02:00
Alexis
5bd76495f4 Added color square to category filter 2025-04-18 17:59:49 +02:00
Alexis
7480447420 Standardized tilde imports for non modules 2025-04-18 17:44:21 +02:00
Alexis
034713961b Changed card color for calendars and worlds 2025-04-18 17:35:02 +02:00
Alexis
00224417be Cleaned up most vanilla try catches 2025-04-18 17:11:45 +02:00
Alexis
9b4556245e Fixed hardcoded world id for calendar creation 2025-04-17 22:34:52 +02:00
Alexis
ad8120bdd0 Removed useless lazy directives 2025-04-17 22:14:10 +02:00
Alexis
73bd79c3c5 Fixed hydration error 2025-04-17 21:47:25 +02:00
Alexis
8215791830 Changed h-w to size 2025-04-17 21:41:48 +02:00
AlexisNP
36456ce377 Merge pull request #76 from AlexisNP/upgrade/tailwind-4
Upgrade/tailwind 4
2025-04-17 19:58:02 +02:00
Alexis
015d0c4db4 Moved bgc colors to relevant file 2025-04-17 17:22:01 +02:00
Alexis
392d8cd8ec Fixed dialog animation issues 2025-04-17 17:02:42 +02:00
Alexis
473c69c9a1 Updated packages 2025-04-17 16:55:51 +02:00
Alexis
790deeec5d Updated border and event colors 2025-04-17 16:42:48 +02:00
Alexis
687b96cf12 Moved event and colored element logic to css variables 2025-04-16 19:52:19 +02:00
Alexis
3e973dd0e9 "Working" config with tailwind 4 and shadcn nuxt 2025-04-16 12:14:27 +02:00
Alexis
cc07f898ea Ran upgrade cli 2025-04-16 12:00:38 +02:00
AlexisNP
e5bc5c8a41 Merge pull request #74 from AlexisNP/features/category-management
Features/category management
2025-04-16 10:59:11 +02:00
164 changed files with 5578 additions and 3945 deletions

28
app.vue
View File

@@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { ConfigProvider } from "radix-vue" import { ConfigProvider } from "radix-vue"
import { cn } from "./lib/utils";
useHead({ useHead({
titleTemplate: (titleChunk) => { titleTemplate: (titleChunk) => {
@@ -26,6 +27,8 @@ useHead({
}) })
const useIdFunction = () => useId() const useIdFunction = () => useId()
const { isSidebarOpened } = storeToRefs(useUiStore())
</script> </script>
<template> <template>
@@ -34,10 +37,18 @@ const useIdFunction = () => useId()
<NuxtLayout> <NuxtLayout>
<ConfigProvider :use-id="useIdFunction"> <ConfigProvider :use-id="useIdFunction">
<div class="h-full grid grid-cols-[auto_1fr] dark:bg-black transition-colors"> <div
class="h-full grid md:grid-cols-[auto_1fr] dark:bg-black shadow-body-light dark:shadow-body-dark transition-colors after:absolute after:transition-colors"
:class="cn({
'max-md:after:bg-transparent': isSidebarOpened,
'has-sidebar max-md:after:bg-black/20 md:after:opacity-0 md:after:pointer-events-none': isSidebarOpened
})"
>
<Sidebar /> <Sidebar />
<div class="wrapper shadow-body-light dark:shadow-body-dark transition-all"> <div
class="wrapper max-h-screen transition-all"
>
<NuxtPage /> <NuxtPage />
</div> </div>
</div> </div>
@@ -54,4 +65,17 @@ const useIdFunction = () => useId()
.wrapper > * { .wrapper > * {
height: 100%; height: 100%;
} }
.has-sidebar {
position: relative;
isolation: isolate;
&::after {
content: '';
display: block;
position: fixed;
inset: 0;
z-index: 30;
}
}
</style> </style>

326
assets/_colors.css Normal file
View File

@@ -0,0 +1,326 @@
:root {
--event-red-foreground: var(--color-slate-900);
--event-red-background: var(--color-red-500);
--event-orange-foreground: var(--color-slate-900);
--event-orange-background: var(--color-orange-500);
--event-amber-foreground: var(--color-slate-900);
--event-amber-background: var(--color-amber-500);
--event-yellow-foreground: var(--color-slate-900);
--event-yellow-background: var(--color-yellow-500);
--event-lime-foreground: var(--color-slate-900);
--event-lime-background: var(--color-lime-500);
--event-green-foreground: var(--color-white);
--event-green-background: var(--color-green-600);
--event-emerald-foreground: var(--color-white);
--event-emerald-background: var(--color-emerald-600);
--event-teal-foreground: var(--color-white);
--event-teal-background: var(--color-teal-600);
--event-cyan-foreground: var(--color-white);
--event-cyan-background: var(--color-cyan-600);
--event-sky-foreground: var(--color-white);
--event-sky-background: var(--color-sky-600);
--event-blue-foreground: var(--color-white);
--event-blue-background: var(--color-blue-600);
--event-indigo-foreground: var(--color-white);
--event-indigo-background: var(--color-indigo-600);
--event-violet-foreground: var(--color-white);
--event-violet-background: var(--color-violet-600);
--event-purple-foreground: var(--color-white);
--event-purple-background: var(--color-purple-600);
--event-fuchsia-foreground: var(--color-white);
--event-fuchsia-background: var(--color-fuchsia-700);
--event-pink-foreground: var(--color-white);
--event-pink-background: var(--color-pink-700);
--event-rose-foreground: var(--color-white);
--event-rose-background: var(--color-pink-700);
--event-black-foreground: var(--color-white);
--event-black-background: var(--color-stone-500);
--event-white-foreground: var(--color-slate-900);
--event-white-background: var(--color-lime-100);
}
@theme inline {
--color-event-red-foreground: var(--event-red-foreground);
--color-event-red-background: var(--event-red-background);
--color-event-orange-foreground: var(--event-orange-foreground);
--color-event-orange-background: var(--event-orange-background);
--color-event-amber-foreground: var(--event-amber-foreground);
--color-event-amber-background: var(--event-amber-background);
--color-event-yellow-foreground: var(--event-yellow-foreground);
--color-event-yellow-background: var(--event-yellow-background);
--color-event-lime-foreground: var(--event-lime-foreground);
--color-event-lime-background: var(--event-lime-background);
--color-event-green-foreground: var(--event-green-foreground);
--color-event-green-background: var(--event-green-background);
--color-event-emerald-foreground: var(--event-emerald-foreground);
--color-event-emerald-background: var(--event-emerald-background);
--color-event-teal-foreground: var(--event-teal-foreground);
--color-event-teal-background: var(--event-teal-background);
--color-event-cyan-foreground: var(--event-cyan-foreground);
--color-event-cyan-background: var(--event-cyan-background);
--color-event-sky-foreground: var(--event-sky-foreground);
--color-event-sky-background: var(--event-sky-background);
--color-event-blue-foreground: var(--event-blue-foreground);
--color-event-blue-background: var(--event-blue-background);
--color-event-indigo-foreground: var(--event-indigo-foreground);
--color-event-indigo-background: var(--event-indigo-background);
--color-event-violet-foreground: var(--event-violet-foreground);
--color-event-violet-background: var(--event-violet-background);
--color-event-purple-foreground: var(--event-purple-foreground);
--color-event-purple-background: var(--event-purple-background);
--color-event-fuchsia-foreground: var(--event-fuchsia-foreground);
--color-event-fuchsia-background: var(--event-fuchsia-background);
--color-event-pink-foreground: var(--event-pink-foreground);
--color-event-pink-background: var(--event-pink-background);
--color-event-rose-foreground: var(--event-rose-foreground);
--color-event-rose-background: var(--event-rose-background);
--color-event-black-foreground: var(--event-black-foreground);
--color-event-black-background: var(--event-black-background);
--color-event-white-foreground: var(--event-white-foreground);
--color-event-white-background: var(--event-white-background);
}
.event-button,
.event-popover,
.event-callout,
.card-color {
--fg-color: var(--color-foreground);
--bg-color: var(--color-background);
transition-duration: var(--default-transition-duration);
transition-timing-function: var(--default-transition-timing-function);
}
.event-callout hr,
.card-color hr {
transition-property: border-color;
transition-duration: var(--default-transition-duration);
transition-timing-function: var(--default-transition-timing-function);
}
.event-button {
color: var(--fg-color);
background-color: var(--bg-color);
text-underline-offset: .15rem;
transition-property: background-color, color;
}
.event-button:hover {
text-decoration: underline;
background-color: color-mix(in srgb, var(--bg-color) 70%, var(--color-background));
}
.event-button.is-hidden {
background-color: transparent;
color: var(--color-foreground);
border: 1px dashed var(--bg-color);
transition-property: background-color, color, border-color;
}
.event-button.is-hidden:is(:hover, :focus-visible) {
background-color: color-mix(in srgb, var(--bg-color) 70%, var(--color-background));
color: var(--fg-color);
}
.event-popover {
background-color: color-mix(in srgb, var(--bg-color) 10%, var(--color-background));
border-color: color-mix(in srgb, var(--bg-color) 50%, var(--color-background));
transition-property: background-color, color;
hr {
border-color: var(--bg-color);
}
}
.event-callout {
position: relative;
background-color: color-mix(in srgb, var(--bg-color) 10%, var(--color-background));
border-top-right-radius: .2rem;
border-bottom-right-radius: .2rem;
transition-property: background-color, color;
hr {
border-color: var(--bg-color);
}
}
.event-callout::before {
display: block;
content: '';
position: absolute;
height: 100%;
inset-block: 0;
left: -0.5rem;
width: .5rem;
background-color: color-mix(in srgb, var(--bg-color) 70%, var(--color-background));
border-top-left-radius: .2rem;
border-bottom-left-radius: .2rem;
transition-property: background-color;
transition-duration: var(--default-transition-duration);
transition-timing-function: var(--default-transition-timing-function);
}
.event-callout:is(:hover, :focus-visible) {
background-color: color-mix(in srgb, var(--bg-color) 30%, var(--color-background));
}
.event-callout:is(:hover, :focus-visible)::before {
background-color: color-mix(in srgb, var(--bg-color) 90%, var(--color-background));
}
.event-callout.is-hidden {
border: 1px dashed var(--bg-color);
background-color: transparent;
}
.event-callout.is-hidden::before {
inset-block: -2px;
height: calc(100% + 3px);
background-color: color-mix(in srgb, var(--bg-color) 40%, var(--color-background));
}
.event-callout.is-hidden:is(:hover, :focus-visible) {
background-color: color-mix(in srgb, var(--bg-color) 5%, var(--color-background));
}
.event-callout.is-hidden:is(:hover, :focus-visible)::before {
background-color: color-mix(in srgb, var(--bg-color) 50%, var(--color-background));
}
.card-color {
border-color: color-mix(in srgb, var(--bg-color) 30%, var(--color-background));
background-color: color-mix(in srgb, var(--bg-color) 10%, var(--color-background));
hr {
border-color: color-mix(in srgb, var(--bg-color) 30%, var(--color-background))
}
}
.card-color:is(:hover, :focus-visible) {
border-color: color-mix(in srgb, var(--bg-color) 40%, var(--color-background));
background-color: color-mix(in srgb, var(--bg-color) 20%, var(--color-background));
hr {
border-color: color-mix(in srgb, var(--bg-color) 40%, var(--color-background))
}
}
.bgc {
display: flex;
align-items: center;
flex-wrap: nowrap;
}
.bgc::before {
content: "";
display: inline-block;
width: .75rem;
aspect-ratio: 1;
margin-right: 0.5em;
border-radius: .25rem;
background-color: var(--bg-color);
border: 1px solid transparent;
}
.element-red {
--fg-color: var(--color-event-red-foreground);
--bg-color: var(--color-event-red-background);
}
.element-orange {
--fg-color: var(--color-event-orange-foreground);
--bg-color: var(--color-event-orange-background);
}
.element-amber {
--fg-color: var(--color-event-amber-foreground);
--bg-color: var(--color-event-amber-background);
}
.element-yellow {
--fg-color: var(--color-event-yellow-foreground);
--bg-color: var(--color-event-yellow-background);
}
.element-lime {
--fg-color: var(--color-event-lime-foreground);
--bg-color: var(--color-event-lime-background);
}
.element-green {
--fg-color: var(--color-event-green-foreground);
--bg-color: var(--color-event-green-background);
}
.element-emerald {
--fg-color: var(--color-event-emerald-foreground);
--bg-color: var(--color-event-emerald-background);
}
.element-teal {
--fg-color: var(--color-event-teal-foreground);
--bg-color: var(--color-event-teal-background);
}
.element-cyan {
--fg-color: var(--color-event-cyan-foreground);
--bg-color: var(--color-event-cyan-background);
}
.element-sky {
--fg-color: var(--color-event-sky-foreground);
--bg-color: var(--color-event-sky-background);
}
.element-blue {
--fg-color: var(--color-event-blue-foreground);
--bg-color: var(--color-event-blue-background);
}
.element-indigo {
--fg-color: var(--color-event-indigo-foreground);
--bg-color: var(--color-event-indigo-background);
}
.element-violet {
--fg-color: var(--color-event-violet-foreground);
--bg-color: var(--color-event-violet-background);
}
.element-purple {
--fg-color: var(--color-event-purple-foreground);
--bg-color: var(--color-event-purple-background);
}
.element-fuchsia {
--fg-color: var(--color-event-fuchsia-foreground);
--bg-color: var(--color-event-fuchsia-background);
}
.element-pink {
--fg-color: var(--color-event-pink-foreground);
--bg-color: var(--color-event-pink-background);
}
.element-rose {
--fg-color: var(--color-event-rose-foreground);
--bg-color: var(--color-event-rose-background);
}
.element-black {
--fg-color: var(--color-event-black-foreground);
--bg-color: var(--color-event-black-background);
}
.element-white {
--fg-color: var(--color-event-white-foreground);
--bg-color: var(--color-event-white-background);
}

4
assets/_typography.css Normal file
View File

@@ -0,0 +1,4 @@
@theme inline {
--text-2xs: 0.7rem;
--text-2xs--line-height: 0.75rem;
}

View File

@@ -1,416 +1,33 @@
@tailwind base; @import 'tailwindcss';
@tailwind components;
@tailwind utilities;
@config '../tailwind.config.js';
@import "./theme.css" layer(theme);
@import "./_typography.css" layer(theme);
@import "./_colors.css" layer(theme);
@import "./_colors.css" layer(utilities);
/*
The default border color has changed to `currentcolor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base { @layer base {
:root { *,
--background: 0 0% 100%; ::after,
--foreground: 222.2 84% 4.9%; ::before,
::backdrop,
--muted: 210 40% 96.1%; ::file-selector-button {
--muted-foreground: 215.4 16.3% 46.9%; border-color: var(--color-border, currentcolor);
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--primary: 245 58% 51%;
--primary-foreground: 0 0% 100%;
--secondary: 210 50% 95%;
--secondary-foreground: 222.2 47.4% 11.2%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--success: 156 72% 67%;
--success-muted: 156 72% 90%;
--success-foreground: 222.2 84% 4.9%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark {
--background: 180 0 4.9%;
--foreground: 210 40% 98%;
--muted: 244 47% 20%;
--muted-foreground: 215 20.2% 65.1%;
--popover: 180 0 4.9%;
--popover-foreground: 210 40% 98%;
--card: 180 0 4.9%;
--card-foreground: 210 40% 98%;
--border: 217 0 10%;
--input: 244 47% 20%;
--primary: 245 58% 51%;
--primary-foreground: 0 0% 100%;
--secondary: 244 47% 20%;
--secondary-foreground: 210 40% 98%;
--accent: 244 47% 20%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--success: 142 72% 29%;
--success-muted: 142 72% 10%;
--success-foreground: 210 40% 98%;
--ring: 212.7 26.8% 83.9%;
} }
} }
@layer base {
* {
@apply border-border;
}
body { body {
@apply bg-background text-foreground; color: var(--color-foreground);
} background-color: var(--color-background);
}
/* Colors */
.event-red:not(.is-hidden) {
@apply text-slate-900 bg-red-500 hover:bg-red-600 focus-visible:bg-red-600
}
.event-orange:not(.is-hidden) {
@apply text-slate-900 bg-orange-500 hover:bg-orange-600 focus-visible:bg-orange-600
}
.event-amber:not(.is-hidden) {
@apply text-slate-900 bg-amber-500 hover:bg-amber-600 focus-visible:bg-amber-600
}
.event-yellow:not(.is-hidden) {
@apply text-slate-900 bg-yellow-600 hover:bg-yellow-700 focus-visible:bg-yellow-700
}
.event-lime:not(.is-hidden) {
@apply text-slate-900 bg-lime-500 hover:bg-lime-600 focus-visible:bg-lime-600
}
.event-green:not(.is-hidden) {
@apply text-white bg-green-600 hover:bg-green-700 focus-visible:bg-green-700
}
.event-emerald:not(.is-hidden) {
@apply text-white bg-emerald-600 hover:bg-emerald-700 focus-visible:bg-emerald-700
}
.event-teal:not(.is-hidden) {
@apply text-white bg-teal-600 hover:bg-teal-700 focus-visible:bg-teal-700
}
.event-cyan:not(.is-hidden) {
@apply text-white bg-cyan-600 hover:bg-cyan-700 focus-visible:bg-cyan-700
}
.event-sky:not(.is-hidden) {
@apply text-white bg-sky-600 hover:bg-sky-700 focus-visible:bg-sky-700
}
.event-blue:not(.is-hidden) {
@apply text-white bg-blue-600 hover:bg-blue-700 focus-visible:bg-blue-700
}
.event-indigo:not(.is-hidden) {
@apply text-white bg-indigo-600 hover:bg-indigo-700 focus-visible:bg-indigo-700
}
.event-violet:not(.is-hidden) {
@apply text-white bg-violet-600 hover:bg-violet-700 focus-visible:bg-violet-700
}
.event-purple:not(.is-hidden) {
@apply text-white bg-purple-600 hover:bg-purple-700 focus-visible:bg-purple-700
}
.event-fuchsia:not(.is-hidden) {
@apply text-white bg-fuchsia-600 hover:bg-fuchsia-700 focus-visible:bg-fuchsia-700
}
.event-pink:not(.is-hidden) {
@apply text-white bg-pink-700 hover:bg-pink-800 focus-visible:bg-pink-800
}
.event-rose:not(.is-hidden) {
@apply text-white bg-rose-700 hover:bg-rose-800 focus-visible:bg-rose-800
}
.event-black:not(.is-hidden) {
@apply text-white bg-stone-500 hover:bg-stone-700 focus-visible:bg-stone-700
}
.event-white:not(.is-hidden) {
@apply text-slate-900 bg-white hover:bg-yellow-200 focus-visible:bg-yellow-200 border-[1px] border-slate-300
}
.event.is-hidden {
border: 1px dashed transparent;
}
.event-red.is-hidden {
@apply text-red-600 border-red-600 hover:bg-red-100 focus-visible:bg-red-100 dark:text-white dark:hover:bg-red-950 dark:focus-visible:bg-red-950
}
.event-orange.is-hidden {
@apply text-orange-600 border-orange-600 hover:bg-orange-100 focus-visible:bg-orange-100 dark:text-white dark:hover:bg-orange-950 dark:focus-visible:bg-orange-950
}
.event-amber.is-hidden {
@apply text-red-600 border-red-600 hover:bg-red-100 focus-visible:bg-red-100 dark:text-white dark:hover:bg-red-950 dark:focus-visible:bg-red-950
}
.event-yellow.is-hidden {
@apply text-yellow-600 border-yellow-600 hover:bg-yellow-100 focus-visible:bg-yellow-100 dark:text-white dark:hover:bg-yellow-950 dark:focus-visible:bg-yellow-950
}
.event-lime.is-hidden {
@apply text-lime-600 border-lime-600 hover:bg-lime-100 focus-visible:bg-lime-100 dark:text-white dark:hover:bg-lime-950 dark:focus-visible:bg-lime-950
}
.event-green.is-hidden {
@apply text-green-600 border-green-600 hover:bg-green-700 focus-visible:bg-green-700 dark:text-white dark:hover:bg-green-950 dark:focus-visible:bg-green-950
}
.event-emerald.is-hidden {
@apply text-emerald-600 border-emerald-600 hover:bg-emerald-700 focus-visible:bg-emerald-700 dark:text-white dark:hover:bg-emerald-950 dark:focus-visible:bg-emerald-950
}
.event-teal.is-hidden {
@apply text-teal-700 border-teal-600 hover:bg-teal-700 focus-visible:bg-teal-700 dark:text-white dark:hover:bg-teal-950 dark:focus-visible:bg-teal-950
}
.event-cyan.is-hidden {
@apply text-cyan-600 border-cyan-600 hover:bg-cyan-100 focus-visible:bg-cyan-100 dark:text-white dark:hover:bg-cyan-950 dark:focus-visible:bg-cyan-950
}
.event-sky.is-hidden {
@apply text-sky-600 border-sky-600 hover:bg-sky-100 focus-visible:bg-sky-100 dark:text-white dark:hover:bg-sky-950 dark:focus-visible:bg-sky-950
}
.event-blue.is-hidden {
@apply text-blue-600 border-blue-600 hover:bg-blue-100 focus-visible:bg-blue-100 dark:text-white dark:hover:bg-blue-950 dark:focus-visible:bg-blue-950
}
.event-indigo.is-hidden {
@apply text-indigo-600 border-indigo-600 hover:bg-indigo-100 focus-visible:bg-indigo-100 dark:text-white dark:hover:bg-indigo-950 dark:focus-visible:bg-indigo-950
}
.event-violet.is-hidden {
@apply text-violet-600 border-violet-600 hover:bg-violet-100 focus-visible:bg-violet-100 dark:text-white dark:hover:bg-violet-950 dark:focus-visible:bg-violet-950
}
.event-purple.is-hidden {
@apply text-purple-600 border-purple-600 hover:bg-purple-100 focus-visible:bg-purple-100 dark:text-white dark:hover:bg-purple-950 dark:focus-visible:bg-purple-950
}
.event-fuchsia.is-hidden {
@apply text-fuchsia-600 border-fuchsia-600 hover:bg-fuchsia-100 focus-visible:bg-fuchsia-100 dark:text-white dark:hover:bg-fuchsia-950 dark:focus-visible:bg-fuchsia-950
}
.event-pink.is-hidden {
@apply text-pink-700 border-pink-700 hover:bg-pink-100 focus-visible:bg-pink-100 dark:text-white dark:hover:bg-pink-950 dark:focus-visible:bg-pink-950
}
.event-rose.is-hidden {
@apply text-rose-700 border-rose-700 hover:bg-rose-100 focus-visible:bg-rose-100 dark:text-white dark:hover:bg-rose-950 dark:focus-visible:bg-rose-950
}
.event-black.is-hidden {
@apply text-stone-700 border-stone-500 hover:bg-stone-100 focus-visible:bg-stone-100 dark:text-white dark:hover:bg-stone-950 dark:focus-visible:bg-stone-950
}
.event-white.is-hidden {
@apply text-slate-900 border-neutral-400 hover:bg-yellow-200 focus-visible:bg-yellow-200 dark:bg-neutral-900 dark:text-white dark:hover:bg-neutral-700 dark:focus-visible:bg-neutral-300
}
.dark .event-details-red {
--base-color: var(--color-red-800);
}
.dark .event-details-orange {
--base-color: var(--color-orange-800);
}
.dark .event-details-amber {
--base-color: var(--color-amber-800);
}
.dark .event-details-yellow {
--base-color: var(--color-yellow-800);
}
.dark .event-details-lime {
--base-color: var(--color-lime-800);
}
.dark .event-details-green {
--base-color: var(--color-green-800);
}
.dark .event-details-emerald {
--base-color: var(--color-emerald-800);
}
.dark .event-details-teal {
--base-color: var(--color-teal-800);
}
.dark .event-details-cyan {
--base-color: var(--color-cyan-800);
}
.dark .event-details-sky {
--base-color: var(--color-sky-800);
}
.dark .event-details-blue {
--base-color: var(--color-blue-800);
}
.dark .event-details-indigo {
--base-color: var(--color-indigo-800);
}
.dark .event-details-violet {
--base-color: var(--color-violet-800);
}
.dark .event-details-purple {
--base-color: var(--color-purple-800);
}
.dark .event-details-fuchsia {
--base-color: var(--color-fuchsia-800);
}
.dark .event-details-pink {
--base-color: var(--color-pink-800);
}
.dark .event-details-rose {
--base-color: var(--color-rose-800);
}
.dark .event-details-black {
--base-color: var(--color-stone-800);
}
.dark .event-details-white {
--base-color: var(--color-white);
}
.event-details-red {
--base-color: var(--color-red-300);
}
.event-details-orange {
--base-color: var(--color-orange-300);
}
.event-details-amber {
--base-color: var(--color-amber-300);
}
.event-details-yellow {
--base-color: var(--color-yellow-300);
}
.event-details-lime {
--base-color: var(--color-lime-300);
}
.event-details-green {
--base-color: var(--color-green-300);
}
.event-details-emerald {
--base-color: var(--color-emerald-300);
}
.event-details-teal {
--base-color: var(--color-teal-300);
}
.event-details-cyan {
--base-color: var(--color-cyan-300);
}
.event-details-sky {
--base-color: var(--color-sky-300);
}
.event-details-blue {
--base-color: var(--color-blue-300);
}
.event-details-indigo {
--base-color: var(--color-indigo-300);
}
.event-details-violet {
--base-color: var(--color-violet-300);
}
.event-details-purple {
--base-color: var(--color-purple-300);
}
.event-details-fuchsia {
--base-color: var(--color-fuchsia-300);
}
.event-details-pink {
--base-color: var(--color-pink-300);
}
.event-details-rose {
--base-color: var(--color-rose-300);
}
.event-details-black {
--base-color: var(--color-stone-300);
}
.event-details-white {
--base-color: var(--color-white);
}
.event-details {
--bg-color: color-mix(in srgb, var(--base-color), var(--color-white) 80%);
--border-color: color-mix(in srgb, var(--base-color), var(--color-white) 50%);
background-color: var(--bg-color);
border-color: var(--border-color);
hr {
border-color: var(--border-color);
}
}
.dark .event-details {
--bg-color: color-mix(in srgb, var(--base-color), var(--color-slate-950) 80%);
--border-color: color-mix(in srgb, var(--base-color), var(--color-slate-800) 50%);
}
.bgc {
display: flex;
align-items: center;
flex-wrap: nowrap;
}
.bgc::before {
content: "";
display: inline-block;
width: .75rem;
aspect-ratio: 1;
margin-right: 0.5em;
border-radius: .25rem;
background-color: red;
border: 1px solid transparent;
}
.bgc-red::before {
@apply bg-red-500;
}
.bgc-orange::before {
@apply bg-orange-500;
}
.bgc-amber::before {
@apply bg-amber-500;
}
.bgc-yellow::before {
@apply bg-yellow-500;
}
.bgc-lime::before {
@apply bg-lime-500;
}
.bgc-green::before {
@apply bg-green-500;
}
.bgc-emerald::before {
@apply bg-emerald-600;
}
.bgc-teal::before {
@apply bg-teal-600;
}
.bgc-cyan::before {
@apply bg-cyan-600;
}
.bgc-sky::before {
@apply bg-sky-600;
}
.bgc-blue::before {
@apply bg-blue-600;
}
.bgc-indigo::before {
@apply bg-indigo-600;
}
.bgc-violet::before {
@apply bg-violet-600;
}
.bgc-purple::before {
@apply bg-purple-600;
}
.bgc-fuchsia::before {
@apply bg-fuchsia-600;
}
.bgc-pink::before {
@apply bg-pink-600;
}
.bgc-rose::before {
@apply bg-rose-600;
}
.bgc-black::before {
@apply bg-black dark:border-[1px] dark:border-slate-300;
}
.bgc-white::before {
@apply bg-white border-[1px] border-slate-700;
} }
.fade-enter-active, .fade-enter-active,

108
assets/theme.css Normal file
View File

@@ -0,0 +1,108 @@
:root {
--background: hsl(0 0% 100%);
--foreground: hsl(222.2 84% 4.9%);
--muted: hsl(210 40% 96.1%);
--muted-foreground: hsl(215.4 16.3% 46.9%);
--popover: hsl(0 0% 100%);
--popover-foreground: hsl(222.2 84% 4.9%);
--card: hsl(0 0% 100%);
--card-foreground: hsl(222.2 84% 4.9%);
--border: hsl(214.3 31.8% 91.4%);
--input: hsl(214.3 31.8% 91.4%);
--primary: hsl(245 58% 51%);
--primary-foreground: hsl(0 0% 100%);
--secondary: hsl(210 50% 95%);
--secondary-foreground: hsl(222.2 47.4% 11.2%);
--accent: hsl(210 40% 96.1%);
--accent-foreground: hsl(222.2 47.4% 11.2%);
--destructive: hsl(0 84.2% 60.2%);
--destructive-foreground: hsl(210 40% 98%);
--success: hsl(156 72% 67%);
--success-muted: hsl(156 72% 90%);
--success-foreground: hsl(222.2 84% 4.9%);
--ring: hsl(222.2 84% 4.9%);
--radius: 0.5rem;
}
.dark {
--background: hsl(180 0 4.9%);
--foreground: hsl(210 40% 98%);
--muted: hsl(0, 0%, 12%);
--muted-foreground: hsl(215 20.2% 65.1%);
--popover: hsl(180 0 4.9%);
--popover-foreground: hsl(210 40% 98%);
--card: hsl(180 0 4.9%);
--card-foreground: hsl(210 40% 98%);
--border: hsl(0, 0%, 16%);
--input: hsl(244 47% 20%);
--primary: hsl(245 58% 51%);
--primary-foreground: hsl(0 0% 100%);
--secondary: hsl(244 47% 20%);
--secondary-foreground: hsl(210 40% 98%);
--accent: hsl(244 47% 20%);
--accent-foreground: hsl(210 40% 98%);
--destructive: hsl(0 62.8% 30.6%);
--destructive-foreground: hsl(210 40% 98%);
--success: hsl(142 72% 29%);
--success-muted: hsl(142 72% 10%);
--success-foreground: hsl(210 40% 98%);
--ring: hsl(212.7 26.8% 83.9%);
--radius: 0.5rem;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-success: var(--success);
--color-success-muted: var(--success-muted);
--color-success-foreground: var(--success-foreground);
--color-ring: var(--ring);
}

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useCalendar } from "@/stores/CalendarStore" import { useCalendar } from "~/stores/CalendarStore"
import { computed, type Component, type ComputedRef } from "vue" import { computed, type Component, type ComputedRef } from "vue"
// import { PhMagnifyingGlass } from '@phosphor-icons/vue' // import { PhMagnifyingGlass } from '@phosphor-icons/vue'
@@ -35,12 +35,12 @@ onMounted(() => {
<template> <template>
<div class="h-full w-full relative"> <div class="h-full w-full relative">
<div class="h-full grid grid-rows-[auto,1fr]"> <div class="h-full grid grid-rows-[auto_1fr]">
<CalendarMenu /> <CalendarMenu />
<component :is="currentViewComponent" /> <component :is="currentViewComponent" />
<LazyCalendarSearch /> <CalendarSearch />
<LazyCalendarDialogCategories v-if="!isReadOnly" /> <LazyCalendarDialogCategories v-if="!isReadOnly" />
<LazyCalendarDialogUpdateEvent v-if="!isReadOnly" /> <LazyCalendarDialogUpdateEvent v-if="!isReadOnly" />
<LazyCalendarDialogDeleteEvent v-if="!isReadOnly" /> <LazyCalendarDialogDeleteEvent v-if="!isReadOnly" />

View File

@@ -9,9 +9,11 @@ const { isReadOnly } = storeToRefs(useCalendar())
<UiTooltipProvider :delay-duration="250"> <UiTooltipProvider :delay-duration="250">
<UiTooltip> <UiTooltip>
<UiTooltipTrigger as-child> <UiTooltipTrigger as-child>
<ClientOnly>
<UiButton v-if="!isReadOnly" variant="secondary" size="icon" @click="toggleCategoriesModal(true)"> <UiButton v-if="!isReadOnly" variant="secondary" size="icon" @click="toggleCategoriesModal(true)">
<PhTag size="20" weight="light" /> <PhTag size="20" weight="light" />
</UiButton> </UiButton>
</ClientOnly>
</UiTooltipTrigger> </UiTooltipTrigger>
<UiTooltipContent> <UiTooltipContent>
<p> <p>

View File

@@ -3,10 +3,15 @@ import { storeToRefs } from "pinia"
import { computed } from "vue" import { computed } from "vue"
import { PhMapPin } from "@phosphor-icons/vue" import { PhMapPin } from "@phosphor-icons/vue"
import { breakpointsTailwind } from "@vueuse/core"
const { defaultDate, getFormattedDateTitle, getRelativeString, getDifferenceInDays } = useCalendar() const { defaultDate, getFormattedDateTitle, getRelativeString, getDifferenceInDays } = useCalendar()
const { selectedDate } = storeToRefs(useCalendar()) const { selectedDate } = storeToRefs(useCalendar())
const breakpoints = useBreakpoints(
breakpointsTailwind
)
const mainDateTitle = computed(() => getFormattedDateTitle(selectedDate.value, true)) const mainDateTitle = computed(() => getFormattedDateTitle(selectedDate.value, true))
// const mainDateTitle = computed(() => convertDateToDays(selectedDate.value)) // const mainDateTitle = computed(() => convertDateToDays(selectedDate.value))
@@ -16,19 +21,24 @@ const isToday = computed(() => getDifferenceInDays(defaultDate, selectedDate.val
<template> <template>
<ClientOnly> <ClientOnly>
<div class="flex gap-2 items-center"> <div class="grid md:flex md:gap-2 items-center">
<h1 class="text-2xl font-bold flex items-center gap-1"> <h1 class="text-lg md:text-2xl max-md:leading-tight font-bold flex items-center gap-1">
<PhMapPin size="26" weight="bold" /> {{ mainDateTitle }} <PhMapPin
:size="breakpoints.md.value ? 26 : 18"
class="max-md:hidden"
weight="light"
/>
{{ mainDateTitle }}
</h1> </h1>
<h2 v-if="!isToday" class="text-xl italic opacity-75"> <h2 v-if="!isToday" class="text-sm max-md:leading-tight md:text-xl italic opacity-75">
{{ dateDifference }} <span class="max-md:hidden"></span> {{ dateDifference }}
</h2> </h2>
</div> </div>
<template #fallback> <template #fallback>
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<UiSkeleton class="h-8 w-64" /> <UiSkeleton class="h-8 w-64 max-md:max-w-full" />
<UiSkeleton class="h-6 w-28" /> <UiSkeleton class="max-md:hidden h-6 w-28" />
</div> </div>
</template> </template>
</ClientOnly> </ClientOnly>

View File

@@ -1,10 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { cn } from "@/lib/utils" import { breakpointsTailwind } from "@vueuse/core"
import { useCalendar } from "@/stores/CalendarStore" import { cn } from "~/lib/utils"
import { PhCalendarBlank, PhCheckCircle, PhGear } from "@phosphor-icons/vue" import { useCalendar } from "~/stores/CalendarStore"
import { computed } from "vue" import { PhCalendarBlank, PhCheckCircle, PhDotsThreeVertical, PhGear, PhTag } from "@phosphor-icons/vue"
const { currentConfig, viewTypeOptions, getViewTypeTitle, setViewType } = useCalendar() const breakpoints = useBreakpoints(
breakpointsTailwind
)
const { currentConfig, viewTypeOptions, getViewTypeTitle, setViewType, toggleCategoriesModal } = useCalendar()
const viewTypeTitle = computed(() => getViewTypeTitle(currentConfig.viewType)) const viewTypeTitle = computed(() => getViewTypeTitle(currentConfig.viewType))
</script> </script>
@@ -12,15 +16,29 @@ const viewTypeTitle = computed(() => getViewTypeTitle(currentConfig.viewType))
<UiDropdownMenu> <UiDropdownMenu>
<UiDropdownMenuTrigger as-child> <UiDropdownMenuTrigger as-child>
<UiButton variant="secondary" size="icon"> <UiButton variant="secondary" size="icon">
<PhGear size="20" weight="fill" /> <ClientOnly>
<PhGear v-if="breakpoints.md.value" size="20" weight="fill" />
<PhDotsThreeVertical v-else size="26" weight="bold" />
</ClientOnly>
</UiButton> </UiButton>
</UiDropdownMenuTrigger> </UiDropdownMenuTrigger>
<UiDropdownMenuContent :side="'bottom'" :align="'start'" :side-offset="10" :align-offset="25" :collision-padding="40" class="text-right"> <UiDropdownMenuContent :side="'bottom'" :align="'start'" :side-offset="10" :align-offset="25" :collision-padding="20" class="text-right">
<UiDropdownMenuArrow /> <UiDropdownMenuArrow />
<UiDropdownMenuLabel> <UiDropdownMenuLabel>
{{ $t('entity.calendar.seeOptions') }} {{ $t('entity.calendar.seeOptions') }}
</UiDropdownMenuLabel> </UiDropdownMenuLabel>
<UiDropdownMenuItem
v-if="!breakpoints.md.value"
class="flex gap-[1ch] justify-end items-center"
@click="toggleCategoriesModal(true)"
>
{{ $t('entity.calendar.seeCategories') }}
<PhTag size="18" />
</UiDropdownMenuItem>
<UiDropdownMenuSub> <UiDropdownMenuSub>
<UiDropdownMenuSubTrigger arrow-direction="left" class="p-0 rounded-none"> <UiDropdownMenuSubTrigger arrow-direction="left" class="p-0 rounded-none">
<UiDropdownMenuItem class="flex gap-[1ch] justify-end items-center pointer-events-none"> <UiDropdownMenuItem class="flex gap-[1ch] justify-end items-center pointer-events-none">

View File

@@ -1,55 +1,51 @@
<script lang="ts" setup> <script lang="ts" setup>
import { PhArchive, PhCalendarDots, PhFile, PhFileDashed, PhFilePlus, PhPencil, PhPencilSimpleLine, PhTrash } from "@phosphor-icons/vue"; import { cn } from "~/lib/utils"
import { DateTime } from "luxon"; import { PhArchive, PhCalendarDots, PhFile, PhFileDashed, PhFilePlus, PhPencil, PhPencilSimpleLine, PhTrash } from "@phosphor-icons/vue"
import type { Calendar } from "~/models/CalendarConfig"; import { DateTime } from "luxon"
import type { Calendar } from "~/models/CalendarConfig"
const props = defineProps<{ const props = defineProps<{
calendar: Calendar, calendar: Calendar
gmId?: string, gmId?: string
showActions?: boolean, showActions?: boolean
}>() }>()
const emit = defineEmits(["on-edit", "on-delete"]) const emit = defineEmits(["on-edit", "on-delete"])
const { locale } = useI18n(); const cardRef = ref(null)
const isCardHovered = useElementHover(cardRef)
const { focused: isCardFocused } = useFocusWithin(cardRef)
const createdAt = DateTime.fromISO(props.calendar.createdAt!).toLocaleString(DateTime.DATETIME_MED, { locale: locale.value }); const { locale } = useI18n()
const updatedAt = computed<string>(() => props.calendar.updatedAt ? DateTime.fromISO(props.calendar.updatedAt).toLocaleString(DateTime.DATETIME_MED, { locale: locale.value }) : "");
const user = useSupabaseUser(); const createdAt = DateTime.fromISO(props.calendar.createdAt!).toLocaleString(DateTime.DATETIME_MED, { locale: locale.value })
const isOwner = computed(() => user.value && props.gmId && user.value.id === props.gmId); const updatedAt = computed<string>(() => props.calendar.updatedAt ? DateTime.fromISO(props.calendar.updatedAt).toLocaleString(DateTime.DATETIME_MED, { locale: locale.value }) : "")
const calendarLink = computed(() => isOwner.value ? `/my/calendars/${props.calendar.id}` : `/calendars/${props.calendar.shortId}`);
const user = useSupabaseUser()
const isOwner = computed(() => user.value && props.gmId && user.value.id === props.gmId)
const calendarLink = computed(() => isOwner.value ? `/my/calendars/${props.calendar.id}` : `/calendars/${props.calendar.shortId}`)
const hasActions = computed(() => isOwner.value && props.showActions)
</script> </script>
<template> <template>
<UiCard <UiCard
class="w-full h-full flex flex-col transition-all hover:bg-slate-50 dark:bg-gray-950 dark:hover:bg-indigo-950 dark:focus-within:outline-gray-900" ref="cardRef"
class="w-full h-full flex flex-col"
:link="calendarLink" :link="calendarLink"
:class="{ :class="cn(
'hover:bg-slate-50 dark:hover:bg-sky-950 dark:focus-within:outline-sky-900': !calendar.color, calendar.color ? `group card-color element-${calendar.color}` : '',
'bg-red-100 dark:bg-red-950 border-red-200 hover:bg-red-50 dark:hover:bg-red-900 dark:border-red-900 dark:focus-within:outline-red-900': calendar.color === 'red', )"
'bg-orange-100 dark:bg-orange-950 border-orange-200 hover:bg-orange-50 dark:hover:bg-orange-900 dark:border-orange-900 dark:focus-within:outline-orange-900': calendar.color === 'orange',
'bg-amber-100 dark:bg-amber-950 border-amber-200 hover:bg-amber-50 dark:hover:bg-amber-900 dark:border-amber-900 dark:focus-within:outline-amber-900': calendar.color === 'amber',
'bg-yellow-100 dark:bg-yellow-950 border-yellow-200 hover:bg-yellow-50 dark:hover:bg-yellow-900 dark:border-yellow-900 dark:focus-within:outline-yellow-900': calendar.color === 'yellow',
'bg-lime-100 dark:bg-lime-950 border-lime-200 hover:bg-lime-50 dark:hover:bg-lime-900 dark:border-lime-900 dark:focus-within:outline-lime-900': calendar.color === 'lime',
' bg-green-100 dark:bg-green-950 border-green-200 hover:bg-green-50 dark:hover:bg-green-900 dark:border-green-900 dark:focus-within:outline-green-900': calendar.color === 'green',
'bg-emerald-100 dark:bg-emerald-950 border-emerald-200 hover:bg-emerald-50 dark:hover:bg-emerald-900 dark:border-emerald-900 dark:focus-within:outline-emerald-900': calendar.color === 'emerald',
'bg-teal-100 dark:bg-teal-950 border-teal-200 hover:bg-teal-50 dark:hover:bg-teal-900 dark:border-teal-900 dark:focus-within:outline-teal-900': calendar.color === 'teal',
'bg-cyan-100 dark:bg-cyan-950 border-cyan-200 hover:bg-cyan-50 dark:hover:bg-cyan-900 dark:border-cyan-900 dark:focus-within:outline-cyan-900': calendar.color === 'cyan',
'bg-sky-100 dark:bg-sky-950 border-sky-200 hover:bg-sky-50 dark:hover:bg-sky-900 dark:border-sky-900 dark:focus-within:outline-sky-900': calendar.color === 'sky',
'bg-blue-100 dark:bg-blue-950 border-blue-200 hover:bg-blue-50 dark:hover:bg-blue-900 dark:border-blue-900 dark:focus-within:outline-blue-900': calendar.color === 'blue',
'bg-indigo-100 dark:bg-indigo-950 border-indigo-200 hover:bg-indigo-50 dark:hover:bg-indigo-900 dark:border-indigo-900 dark:focus-within:outline-indigo-900': calendar.color === 'indigo',
'bg-violet-100 dark:bg-violet-950 border-violet-200 hover:bg-violet-50 dark:hover:bg-violet-900 dark:border-violet-900 dark:focus-within:outline-violet-900': calendar.color === 'violet',
'bg-purple-100 dark:bg-purple-950 border-purple-200 hover:bg-purple-50 dark:hover:bg-purple-900 dark:border-purple-900 dark:focus-within:outline-purple-900': calendar.color === 'purple',
'bg-fuchsia-100 dark:bg-fuchsia-950 border-fuchsia-200 hover:bg-fuchsia-50 dark:hover:bg-fuchsia-900 dark:border-fuchsia-900 dark:focus-within:outline-fuchsia-900': calendar.color === 'fuchsia',
'bg-pink-100 dark:bg-pink-950 border-pink-200 hover:bg-pink-50 dark:hover:bg-pink-900 dark:border-pink-900 dark:focus-within:outline-pink-900': calendar.color === 'pink',
'bg-pink-100 dark:bg-rose-950 border-rose-200 hover:bg-rose-50 dark:hover:bg-rose-900 dark:border-rose-900 dark:focus-within:outline-rose-900': calendar.color === 'rose',
'text-slate-100 bg-slate-900 border-slate-700 hover:bg-slate-700 dark:hover:bg-slate-800 dark:border-slate-900 dark:focus-within:outline-slate-900': calendar.color === 'black',
' hover:bg-slate-50 dark:hover:text-slate-900 dark:hover:bg-slate-200 dark:border-slate-700 dark:focus-within:outline-slate-100': calendar.color === 'white',
}"
> >
<UiCardHeader class="gap-4"> <UiCardHeader class="gap-4">
<UiCardTitle class="text-xl pr-12">{{ calendar.name }}</UiCardTitle> <UiCardTitle
class="text-xl"
:class="cn({
'pr-18': hasActions
})"
>
{{ calendar.name }}
</UiCardTitle>
<div v-if="calendar.state === 'published'" class="flex items-center gap-1 text-sm"> <div v-if="calendar.state === 'published'" class="flex items-center gap-1 text-sm">
<PhFile size="20" weight="fill" /> <PhFile size="20" weight="fill" />
@@ -73,20 +69,25 @@ const calendarLink = computed(() => isOwner.value ? `/my/calendars/${props.calen
</p> </p>
<div <div
v-if="isOwner && showActions" v-if="hasActions && (isCardHovered || isCardFocused)"
class="flex gap-1 absolute top-4 right-4 z-20" class="flex gap-2 absolute top-4 right-4 z-20"
> >
<UiButton size="icon" variant="ghost" class=" hover:text-white hover:bg-indigo-400 dark:hover:bg-indigo-700" @click="emit('on-edit')"> <Transition name="fade" appear>
<PhPencil size="16" /> <UiButton size="icon" variant="outline" class="size-8 border-foreground/20 group-hover:border-foreground/30 hover:text-background hover:border-foreground hover:bg-foreground" @click="emit('on-edit')">
<PhPencil size="15" weight="fill" />
</UiButton> </UiButton>
</Transition>
<UiButton size="icon" variant="ghost" class=" hover:text-white hover:bg-rose-400 dark:hover:bg-rose-700" @click="emit('on-delete')"> <Transition name="fade" appear>
<PhTrash size="16" /> <UiButton size="icon" variant="outline" class="size-8 border-foreground/25 group-hover:border-foreground/40 hover:text-background hover:border-rose-500 hover:bg-rose-500" @click="emit('on-delete')">
<PhTrash size="15" weight="fill" />
</UiButton> </UiButton>
</Transition>
</div> </div>
</UiCardContent> </UiCardContent>
<UiCardFooter> <hr>
<UiCardFooter class="border-t-0">
<ul class="grid gap-1 text-sm"> <ul class="grid gap-1 text-sm">
<li class="flex gap-1 items-center"> <li class="flex gap-1 items-center">
<PhFilePlus size="18" /> <PhFilePlus size="18" />

View File

@@ -0,0 +1,28 @@
<script lang="ts" setup>
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core"
import { PhMagnifyingGlass } from "@phosphor-icons/vue"
const { revealAdvancedSearch } = useCalendar()
const breakpoints = useBreakpoints(
breakpointsTailwind
)
</script>
<template>
<ClientOnly>
<Transition name="fade" appear>
<UiButton
:search-slash="breakpoints.lg.value"
:size="breakpoints.lg.value ? 'default' : 'icon'"
@click="revealAdvancedSearch()"
>
<PhMagnifyingGlass size="20" weight="light" />
<span v-if="breakpoints.lg.value">
{{ $t('entity.advancedSearch.title') }}
</span>
</UiButton>
</Transition>
</ClientOnly>
</template>

View File

@@ -2,8 +2,6 @@
import type { Category } from "~/models/Category"; import type { Category } from "~/models/Category";
import { ScrollAreaRoot, ScrollAreaViewport, ScrollAreaScrollbar, ScrollAreaThumb } from "radix-vue" import { ScrollAreaRoot, ScrollAreaViewport, ScrollAreaScrollbar, ScrollAreaThumb } from "radix-vue"
const { t } = useI18n()
const { categories } = defineProps<{ const { categories } = defineProps<{
categories: Category[] categories: Category[]
}>() }>()
@@ -27,7 +25,7 @@ function closeDeleteDialog() {
<ScrollAreaRoot class="h-36 grow overflow-hidden "> <ScrollAreaRoot class="h-36 grow overflow-hidden ">
<ScrollAreaViewport class="w-full h-full pr-4" as-child> <ScrollAreaViewport class="w-full h-full pr-4" as-child>
<div class="[&:last-child]:border-0"> <div class="last:border-0">
<CalendarCategoryTableRow <CalendarCategoryTableRow
v-for="item in sortedCategories" v-for="item in sortedCategories"
:key="item.id" :key="item.id"

View File

@@ -109,7 +109,7 @@ async function submitNew() {
ref="inputRef" ref="inputRef"
v-model="categorySkeleton.name" v-model="categorySkeleton.name"
type="text" type="text"
class="p-1 h-full w-full bg-transparent focus-visible:outline-none italic" class="p-1 h-full w-full bg-transparent focus-visible:outline-hidden italic"
> >
</template> </template>
</div> </div>

View File

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

View File

@@ -127,7 +127,7 @@ function handleQueryDelete() {
ref="inputRef" ref="inputRef"
v-model="categorySkeleton.name" v-model="categorySkeleton.name"
type="text" type="text"
class="p-1 h-full w-full bg-transparent focus-visible:outline-none italic" class="p-1 h-full w-full bg-transparent focus-visible:outline-hidden italic"
> >
</template> </template>
</div> </div>
@@ -139,7 +139,7 @@ function handleQueryDelete() {
> >
<span <span
class="bgc" class="bgc"
:class="cn(`bgc-${category.color}`)" :class="cn(`element-${category.color}`)"
> >
{{ $t(`ui.colors.${category.color}`) }} {{ $t(`ui.colors.${category.color}`) }}
</span> </span>

View File

@@ -41,7 +41,7 @@ function handleClose() {
<PhX size="20" /> <PhX size="20" />
</UiButton> </UiButton>
<CalendarFormCreate @on-changed-name="onChangedName" @on-close="handleClose" /> <CalendarFormCreate v-if="world.id" :world-id="world.id" @on-changed-name="onChangedName" @on-close="handleClose" />
</UiAlertDialogContent> </UiAlertDialogContent>
</UiAlertDialog> </UiAlertDialog>
</template> </template>

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { RPGDate } from "~/models/Date"; import type { RPGDate } from "~/models/Date";
const { eventSkeleton, operationInProgress } = storeToRefs(useCalendar()) const { eventSkeleton, operationInProgress, currentRPGDate } = storeToRefs(useCalendar())
const { resetSkeleton } = useCalendar() const { resetSkeleton } = useCalendar()
const popoverOpen = ref(false) const popoverOpen = ref(false)
@@ -39,6 +39,13 @@ function handleClosing() {
popoverOpen.value = false popoverOpen.value = false
resetSkeleton() resetSkeleton()
} }
// If the date changes, the popover should close
// Only for months for now (because it's the only view)
watch(currentRPGDate, () => {
if (!popoverOpen.value) return
handleClosing()
})
</script> </script>
<template> <template>

View File

@@ -22,27 +22,26 @@ async function handleAction(): Promise<void> {
isLoading.value = true isLoading.value = true
try { const { error } = await tryCatch($fetch(`/api/calendars/${props.calendar.id}`, { method: "DELETE" }))
await $fetch(`/api/calendars/${props.calendar.id}`, { method: "DELETE" })
emit("on-close") if (error) {
toast({
title: error.message,
variant: "destructive"
})
isLoading.value = false
return
}
toast({ toast({
title: t("entity.calendar.deletedToast.title", { calendar: props.calendar.name }), title: t("entity.calendar.deletedToast.title", { calendar: props.calendar.name }),
variant: "success", variant: "success",
duration: ToastLifetime.SHORT duration: ToastLifetime.SHORT
}) })
} catch (err) {
console.log(err) emit("on-close")
if (err instanceof Error) {
toast({
title: err.message,
variant: "destructive"
})
}
} finally {
isLoading.value = false isLoading.value = false
} }
}
/** /**
* Prevents the modal from closing if's still loading * Prevents the modal from closing if's still loading

View File

@@ -1,5 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core"
import { PhPlus } from "@phosphor-icons/vue"; import { PhPlus } from "@phosphor-icons/vue";
import { VisuallyHidden } from "radix-vue";
const isDialogOpen = ref<boolean>(false); const isDialogOpen = ref<boolean>(false);
const { resetSkeleton } = useCalendar(); const { resetSkeleton } = useCalendar();
@@ -15,26 +17,46 @@ function toggleDialog() {
function handleClosing() { function handleClosing() {
setTimeout(() => resetSkeleton(), 100) setTimeout(() => resetSkeleton(), 100)
} }
const breakpoints = useBreakpoints(
breakpointsTailwind
)
</script> </script>
<template> <template>
<UiButton @click="toggleDialog"> <ClientOnly>
<PhPlus size="18" weight="bold" /> <Transition name="fade" appear>
<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"
>
<PhPlus :size="breakpoints.md.value ? 18 : 24" weight="bold" />
<strong class="font-semibold"> <strong v-if="breakpoints.md.value" class="font-semibold">
{{ $t("entity.calendar.event.newEvent") }} {{ $t("entity.calendar.event.newEvent") }}
</strong> </strong>
</UiButton> </UiButton>
</Transition>
</ClientOnly>
<UiDialog v-model:open="isDialogOpen"> <UiDialog v-model:open="isDialogOpen">
<UiDialogContent <UiDialogContent
class="border-indigo-200 dark:bg-slate-950 dark:border-indigo-950" class="max-md:translate-0 max-md:inset-0 max-md:w-full max-md:block"
@escape-key-down.prevent="handleClosing" :trap-focus="true"
@escape-key-down="handleClosing"
@pointer-down-outside.prevent="handleClosing" @pointer-down-outside.prevent="handleClosing"
> >
<UiDialogTitle> <UiDialogTitle class="max-md:mb-8">
{{ $t("entity.calendar.event.addSingle") }} {{ $t("entity.calendar.event.addSingle") }}
</UiDialogTitle> </UiDialogTitle>
<VisuallyHidden>
<UiDialogDescription>
{{ $t("entity.calendar.event.addSingleDescription") }}
</UiDialogDescription>
</VisuallyHidden>
<CalendarFormCreateEvent @event-created="toggleDialog" /> <CalendarFormCreateEvent @event-created="toggleDialog" />
</UiDialogContent> </UiDialogContent>
</UiDialog> </UiDialog>

View File

@@ -25,13 +25,13 @@ function handleClosing() {
<UiDialogContent <UiDialogContent
:disable-outside-pointer-events="true" :disable-outside-pointer-events="true"
:trap-focus="true" :trap-focus="true"
class="pl-3 min-w-96 border-indigo-200 dark:bg-slate-950 dark:border-indigo-950" class="pl-3 md:min-w-96 max-md:translate-0 max-md:inset-0 max-md:w-full max-md:block"
@escape-key-down="handleClosing" @escape-key-down="handleClosing"
@focus-outside="handleClosing" @focus-outside="handleClosing"
@interact-outside="handleClosing" @interact-outside="handleClosing"
@pointer-down-outside="(e) => e.preventDefault()" @pointer-down-outside="(e) => e.preventDefault()"
> >
<header class="pl-8 grid gap-y-2"> <header class="pl-8 grid gap-y-2 max-md:mb-8">
<UiDialogTitle> <UiDialogTitle>
{{ $t('entity.calendar.event.editDialog.title') }} {{ $t('entity.calendar.event.editDialog.title') }}
</UiDialogTitle> </UiDialogTitle>

View File

@@ -1,8 +1,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
import type { RPGDate } from "@/models/Date" import type { RPGDate } from "~/models/Date"
import type { CalendarEvent } from "@/models/CalendarEvent" import type { CalendarEvent } from "~/models/CalendarEvent"
import { useCalendar } from "@/stores/CalendarStore" import { useCalendar } from "~/stores/CalendarStore"
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core"
import { import {
PhHourglassMedium, PhHourglassMedium,
@@ -13,6 +14,7 @@ import {
PhDotsThreeOutlineVertical, PhDotsThreeOutlineVertical,
PhEye PhEye
} from "@phosphor-icons/vue" } from "@phosphor-icons/vue"
import type { CollisionPadding } from "~/models/Popover"
const { defaultDate, getFormattedDateTitle, jumpToDate, getRelativeString, revealEditEventModal, revealDeleteEventModal } = useCalendar() const { defaultDate, getFormattedDateTitle, jumpToDate, getRelativeString, revealEditEventModal, revealDeleteEventModal } = useCalendar()
const { lastActiveEvent, isReadOnly } = storeToRefs(useCalendar()) const { lastActiveEvent, isReadOnly } = storeToRefs(useCalendar())
@@ -24,6 +26,22 @@ const props = defineProps<{
isEndEvent?: boolean isEndEvent?: boolean
}>() }>()
const breakpoints = useBreakpoints(
breakpointsTailwind
)
const collisionPadLg: CollisionPadding = {
top: 50,
bottom: 50,
left: 50,
right: 50,
}
const collisionPad: CollisionPadding = {
top: 80,
bottom: 80,
left: 12.5,
right: 12.5,
}
// Ref for the popover // Ref for the popover
const eventDetails = ref<HTMLElement>() const eventDetails = ref<HTMLElement>()
@@ -62,17 +80,14 @@ function deployDeleteModal() {
<template> <template>
<UiPopoverContent <UiPopoverContent
class="w-96" class="max-w-full w-96 event-popover"
:align="'center'" :align="'center'"
:align-offset="50" :align-offset="breakpoints.lg.value ? 50 : 25"
:side="'left'" :side-offset="8"
:collision-padding="60" :side="breakpoints.lg.value ? 'left' : 'top'"
:hide-when-detached="true" :collision-padding="breakpoints.lg.value ? collisionPadLg : collisionPad"
:class="cn( :class="cn(
event.category ? `event-details-${event.category.color}` : '', event.category ? `element-${event.category.color}` : '',
{
'event-details': event.category
}
)" )"
@focus-outside.prevent @focus-outside.prevent
> >
@@ -143,7 +158,7 @@ function deployDeleteModal() {
</template> </template>
<template v-if="event.description"> <template v-if="event.description">
<hr class="border-slate-500 mt-2" > <hr class="border-border mt-2" >
<div class="max-h-48 overflow-y-auto mt-2 text-sm text-slate-600 dark:text-slate-300"> <div class="max-h-48 overflow-y-auto mt-2 text-sm text-slate-600 dark:text-slate-300">
{{ event.description }} {{ event.description }}

View File

@@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
import type { RPGDate } from "@/models/Date" import type { RPGDate } from "~/models/Date"
import type { CalendarEvent } from "~/models/CalendarEvent" import type { CalendarEvent } from "~/models/CalendarEvent"
const props = defineProps<{ const props = defineProps<{
@@ -55,10 +55,10 @@ onMounted(() => {
<UiPopover v-model:open="isPopoverDetailsOpen"> <UiPopover v-model:open="isPopoverDetailsOpen">
<UiPopoverTrigger as-child> <UiPopoverTrigger as-child>
<button <button
class="event text-xs px-2 py-1 block w-full text-left rounded-sm transition-colors outline-offset-1" class="event-button text-2xs md:text-xs px-[5px] py-[5px] md:px-2 md:py-1 block w-full text-left rounded-sm transition-colors outline-offset-1 cursor-pointer"
:class=" :class="
cn( cn(
event.category ? `event-${event.category.color}` : '', `element-${event.category?.color}`,
{ {
'is-hidden': event.hidden, 'is-hidden': event.hidden,
'rounded-r-none': isStartEvent, 'rounded-r-none': isStartEvent,
@@ -75,7 +75,7 @@ onMounted(() => {
</button> </button>
</UiPopoverTrigger> </UiPopoverTrigger>
<LazyCalendarEventDetails <CalendarEventDetails
:event :event
:spans-multiple-days :spans-multiple-days
:is-start-event :is-start-event

View File

@@ -2,7 +2,11 @@
import type { Calendar } from "~/models/CalendarConfig"; import type { Calendar } from "~/models/CalendarConfig";
import { PhAlarm, PhCalendarDots, PhCircleNotch, PhWrench } from "@phosphor-icons/vue"; import { PhAlarm, PhCalendarDots, PhCircleNotch, PhWrench } from "@phosphor-icons/vue";
const defaultSkeleton: Calendar = { name: "", today: { day: 1, month: 0, year: 0 }, months: [], events: [], state: "draft", color: "white" } const props = defineProps<{
worldId: number
}>()
const defaultSkeleton: Calendar = { name: "", today: { day: 1, month: 0, year: 0 }, months: [], events: [], categories: [], state: "draft", color: "white" }
const calendarSkeleton = ref<Calendar>({ ...defaultSkeleton }) const calendarSkeleton = ref<Calendar>({ ...defaultSkeleton })
onMounted(() => { onMounted(() => {
@@ -38,17 +42,19 @@ const validSkeleton = computed(() => validSkeletonGeneral.value && validSkeleton
const isCreatingCalendar = ref<boolean>(false) const isCreatingCalendar = ref<boolean>(false)
async function handleSubmit() { async function handleSubmit() {
try {
isCreatingCalendar.value = true isCreatingCalendar.value = true
await $fetch("/api/calendars/create", { method: "POST", body: { ...calendarSkeleton.value, worldId: 1 } })
const { error } = await tryCatch($fetch("/api/calendars/create", { method: "POST", body: { ...calendarSkeleton.value, worldId: props.worldId } }))
if (error) {
console.log(error.message)
isCreatingCalendar.value = false
return
}
emit("on-close") emit("on-close")
} catch (err) {
console.log(err)
} finally {
isCreatingCalendar.value = false isCreatingCalendar.value = false
} }
}
/** /**
* === Watch for name changes to display above === * === Watch for name changes to display above ===
@@ -105,7 +111,7 @@ function handleFormCancel() {
name="new-calendar-name" name="new-calendar-name"
required required
:placeholder="$t('common.title')" :placeholder="$t('common.title')"
class="w-full py-2 -mx-1 px-1 text-xl border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600" class="w-full py-2 -mx-1 px-1 text-xl border-b-[1px] bg-transparent focus-visible:outline-hidden focus-visible:border-blue-600"
@input="handleNameChange" @input="handleNameChange"
> >

View File

@@ -29,18 +29,17 @@ async function handleSubmit() {
isLoading.value = true isLoading.value = true
try { const { error } = await tryCatch(submitSkeleton())
await submitSkeleton()
if (error) {
formErrors.message = error.message
isLoading.value = false
return
}
emit("event-created") emit("event-created")
} catch (err) {
if (err instanceof Error) {
formErrors.message = err.message
}
} finally {
isLoading.value = false isLoading.value = false
} }
}
/** /**
* Click on the cancel button * Click on the cancel button
@@ -67,7 +66,7 @@ function handleCancel() {
:placeholder="$t('entity.calendar.event.title')" :placeholder="$t('entity.calendar.event.title')"
:maxlength="120" :maxlength="120"
pattern="([A-Za-zÀ-ÖØ-öø-ÿ0-9\s\&\-\~]+){3,120}" pattern="([A-Za-zÀ-ÖØ-öø-ÿ0-9\s\&\-\~]+){3,120}"
class="w-full -my-1 py-1 -mx-1 px-1 text-lg border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600 invalid:border-red-500" class="w-full -my-1 py-1 -mx-1 px-1 text-lg border-b-[1px] bg-transparent focus-visible:outline-hidden focus-visible:border-blue-600 invalid:border-red-500"
> >
<div class="mt-2 mb-1 text-xs opacity-50"> <div class="mt-2 mb-1 text-xs opacity-50">
{{ t('entity.calendar.event.patterns.title') }} {{ t('entity.calendar.event.patterns.title') }}
@@ -81,7 +80,7 @@ function handleCancel() {
name="new-event-description" name="new-event-description"
:placeholder="$t('entity.addDescription')" :placeholder="$t('entity.addDescription')"
:maxlength="1200" :maxlength="1200"
class="w-full -my-1 py-1 -mx-1 px-1 min-h-24 max-h-36 text-sm border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600 invalid:border-red-500" class="w-full -my-1 py-1 -mx-1 px-1 min-h-24 max-h-36 text-sm border-b-[1px] bg-transparent focus-visible:outline-hidden focus-visible:border-blue-600 invalid:border-red-500"
/> />
<div class="mt-2 mb-1 text-xs opacity-50"> <div class="mt-2 mb-1 text-xs opacity-50">
{{ t('entity.calendar.event.patterns.description') }} {{ t('entity.calendar.event.patterns.description') }}
@@ -141,7 +140,7 @@ function handleCancel() {
:placeholder="$t('entity.calendar.event.addLocation')" :placeholder="$t('entity.calendar.event.addLocation')"
:maxlength="160" :maxlength="160"
pattern="([A-Za-zÀ-ÖØ-öø-ÿ0-9\s\&\-\~]+){3,160}" pattern="([A-Za-zÀ-ÖØ-öø-ÿ0-9\s\&\-\~]+){3,160}"
class="w-full -my-1 py-2 px-2 text-sm border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600" class="w-full -my-1 py-2 px-2 text-sm border-b-[1px] bg-transparent focus-visible:outline-hidden focus-visible:border-blue-600"
> >
<div class="mt-2 mb-1 text-xs opacity-50"> <div class="mt-2 mb-1 text-xs opacity-50">
{{ t('entity.calendar.event.patterns.location') }} {{ t('entity.calendar.event.patterns.location') }}

View File

@@ -22,22 +22,21 @@ async function handleAction(): Promise<void> {
const categoryName = categorySkeleton.value?.name const categoryName = categorySkeleton.value?.name
try { const { error } = await tryCatch(deleteCategoryFromSkeleton())
await deleteCategoryFromSkeleton()
if (error) {
formErrors.message = error.message
isLoading.value = false
return
}
toast({ toast({
title: t("entity.category.deletedToast.title", { category: categoryName }), title: t("entity.category.deletedToast.title", { category: categoryName }),
variant: "success", variant: "success",
duration: ToastLifetime.MEDIUM duration: ToastLifetime.MEDIUM
}) })
} catch (err) {
if (err instanceof Error) {
formErrors.message = err.message
}
} finally {
isLoading.value = false isLoading.value = false
} }
}
/** /**
* Click on the cancel button * Click on the cancel button

View File

@@ -22,8 +22,13 @@ async function handleAction(): Promise<void> {
const eventTitle = eventSkeleton.value.title const eventTitle = eventSkeleton.value.title
try { const { error } = await tryCatch(deleteEventFromSkeleton())
await deleteEventFromSkeleton()
if (error) {
formErrors.message = error.message
isLoading.value = false
return
}
isDeleteEventModalOpen.value = false isDeleteEventModalOpen.value = false
resetSkeleton() resetSkeleton()
@@ -33,14 +38,8 @@ async function handleAction(): Promise<void> {
variant: "success", variant: "success",
duration: ToastLifetime.MEDIUM duration: ToastLifetime.MEDIUM
}) })
} catch (err) {
if (err instanceof Error) {
formErrors.message = err.message
}
} finally {
isLoading.value = false isLoading.value = false
} }
}
/** /**
* Click on the cancel button * Click on the cancel button

View File

@@ -34,17 +34,23 @@ const validSkeleton = computed(() => validSkeletonGeneral.value && validSkeleton
const isUpdatingCalendar = ref<boolean>(false) const isUpdatingCalendar = ref<boolean>(false)
async function handleSubmit() { async function handleSubmit() {
try {
isUpdatingCalendar.value = true isUpdatingCalendar.value = true
await $fetch(`/api/calendars/${calendarSkeleton.value.id}`, { method: "PATCH", body: { ...calendarSkeleton.value, worldId: props.world?.id } })
const fetchBody = { ...calendarSkeleton.value, worldId: props.world?.id }
const { error } = await tryCatch(
$fetch(`/api/calendars/${calendarSkeleton.value.id}`, { method: "PATCH", body: fetchBody })
)
if (error) {
console.log(error.message)
isUpdatingCalendar.value = false
return
}
emit("on-close") emit("on-close")
} catch (err) {
console.log(err)
} finally {
isUpdatingCalendar.value = false isUpdatingCalendar.value = false
} }
}
/** /**
* === Watch for name changes to display above === * === Watch for name changes to display above ===
@@ -101,7 +107,7 @@ function handleFormCancel() {
name="new-calendar-name" name="new-calendar-name"
required required
:placeholder="$t('common.title')" :placeholder="$t('common.title')"
class="w-full py-2 -mx-1 px-1 text-xl border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600" class="w-full py-2 -mx-1 px-1 text-xl border-b-[1px] bg-transparent focus-visible:outline-hidden focus-visible:border-blue-600"
@input="handleNameChange" @input="handleNameChange"
> >

View File

@@ -23,19 +23,11 @@ async function handleAction() {
isLoading.value = true isLoading.value = true
try { const { error } = await tryCatch(updateEventFromSkeleton())
await updateEventFromSkeleton()
emit("event-updated") if (error) {
toast({
title: t("entity.calendar.event.updatedToast.title", { event: eventSkeleton.value.title }),
variant: "success",
duration: ToastLifetime.SHORT
})
} catch (err) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const apiError = (err as any).data as APIError const apiError = (error as any).data as APIError
apiError.data.errors.forEach((error) => { apiError.data.errors.forEach((error) => {
toast({ toast({
@@ -45,10 +37,21 @@ async function handleAction() {
duration: ToastLifetime.MEDIUM, duration: ToastLifetime.MEDIUM,
}) })
}) })
} finally {
resetSkeleton()
isLoading.value = false isLoading.value = false
return
} }
emit("event-updated")
toast({
title: t("entity.calendar.event.updatedToast.title", { event: eventSkeleton.value.title }),
variant: "success",
duration: ToastLifetime.SHORT
})
isLoading.value = false
resetSkeleton()
} }
/** /**
@@ -79,7 +82,7 @@ function handleCancel() {
:placeholder="$t('entity.calendar.event.title')" :placeholder="$t('entity.calendar.event.title')"
:minlength="3" :minlength="3"
:maxlength="120" :maxlength="120"
class="w-full -my-1 py-1 -mx-1 px-1 text-lg border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600 invalid:border-red-500" class="w-full -my-1 py-1 -mx-1 px-1 text-lg border-b-[1px] bg-transparent focus-visible:outline-hidden focus-visible:border-blue-600 invalid:border-red-500"
> >
<div class="mt-2 mb-1 text-xs opacity-50"> <div class="mt-2 mb-1 text-xs opacity-50">
{{ t('entity.calendar.event.patterns.title') }} {{ t('entity.calendar.event.patterns.title') }}
@@ -95,7 +98,7 @@ function handleCancel() {
name="new-event-description" name="new-event-description"
:placeholder="$t('entity.addDescription')" :placeholder="$t('entity.addDescription')"
:maxlength="1200" :maxlength="1200"
class="w-full -my-1 py-1 -mx-1 px-1 min-h-24 max-h-36 text-sm border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600 invalid:border-red-500" class="w-full -my-1 py-1 -mx-1 px-1 min-h-24 max-h-36 text-sm border-b-[1px] bg-transparent focus-visible:outline-hidden focus-visible:border-blue-600 invalid:border-red-500"
/> />
<div class="mt-2 mb-1 text-xs opacity-50"> <div class="mt-2 mb-1 text-xs opacity-50">
{{ t('entity.calendar.event.patterns.description') }} {{ t('entity.calendar.event.patterns.description') }}
@@ -159,7 +162,7 @@ function handleCancel() {
:placeholder="$t('entity.calendar.event.addLocation')" :placeholder="$t('entity.calendar.event.addLocation')"
:minlength="3" :minlength="3"
:maxlength="160" :maxlength="160"
class="w-full -my-1 py-2 px-2 text-sm border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600 invalid:border-red-500" class="w-full -my-1 py-2 px-2 text-sm border-b-[1px] bg-transparent focus-visible:outline-hidden focus-visible:border-blue-600 invalid:border-red-500"
> >
<div class="mt-2 mb-1 text-xs opacity-50"> <div class="mt-2 mb-1 text-xs opacity-50">

View File

@@ -50,7 +50,7 @@ const filteredCategories = computed(() =>
</ul> </ul>
</template> </template>
<PhCaretDown class="ml-2 h-4 w-4 shrink-0 opacity-50" /> <PhCaretDown class="ml-2 size-4 shrink-0 opacity-50" />
</UiButton> </UiButton>
</UiPopoverTrigger> </UiPopoverTrigger>
<UiPopoverContent <UiPopoverContent

View File

@@ -1,5 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { PhCaretDown } from "@phosphor-icons/vue"; import { PhCaretDown } from "@phosphor-icons/vue";
import { cn } from "~/lib/utils";
import type { Category } from "~/models/Category"; import type { Category } from "~/models/Category";
const isPopoverOpen = ref<boolean>(false) const isPopoverOpen = ref<boolean>(false)
@@ -39,10 +40,15 @@ const filteredCategories = computed(() =>
{{ props.placeholder }} {{ props.placeholder }}
</template> </template>
<template v-else> <template v-else>
{{ model.name }} <span
class="bgc"
:class="cn(`element-${model.color}`)"
>
{{ capitalize(model.name) }}
</span>
</template> </template>
<PhCaretDown class="ml-2 h-4 w-4 shrink-0 opacity-50" /> <PhCaretDown class="ml-2 size-4 shrink-0 opacity-50" />
</UiButton> </UiButton>
</UiPopoverTrigger> </UiPopoverTrigger>
<UiPopoverContent <UiPopoverContent
@@ -51,7 +57,7 @@ const filteredCategories = computed(() =>
:collision-padding="50" :collision-padding="50"
class="w-fit h-[33vh] p-0" class="w-fit h-[33vh] p-0"
> >
<UiCommand v-model="model" v-model:searchTerm="searchTerm"> <UiCommand v-model="model" v-model:search-term="searchTerm">
<UiCommandInput :placeholder="$t('entity.category.search')" /> <UiCommandInput :placeholder="$t('entity.category.search')" />
<UiCommandEmpty>{{ $t('entity.category.notFoundAny') }}</UiCommandEmpty> <UiCommandEmpty>{{ $t('entity.category.notFoundAny') }}</UiCommandEmpty>
<UiCommandList> <UiCommandList>
@@ -63,7 +69,12 @@ const filteredCategories = computed(() =>
class="cursor-pointer" class="cursor-pointer"
@select="handleCatSelect" @select="handleCatSelect"
> >
{{ category.name }} <span
class="bgc"
:class="cn(`element-${category.color}`)"
>
{{ capitalize(category.name) }}
</span>
</UiCommandItem> </UiCommandItem>
</UiCommandGroup> </UiCommandGroup>
</UiCommandList> </UiCommandList>

View File

@@ -91,19 +91,19 @@ useSortable(monthSortableList, model.value, { animation: 150, handle: ".handle"
/> />
</div> </div>
<div class="md:col-span-1"> <div class="md:col-span-1">
<UiButton size="icon" class="rounded-full h-8 w-8" :disabled="!validNewMonth" @click.prevent="addMonthToModel"> <UiButton size="icon" class="rounded-full size-8" :disabled="!validNewMonth" @click.prevent="addMonthToModel">
<PhPlus size="17"/> <PhPlus size="17"/>
</UiButton> </UiButton>
</div> </div>
<div class="md:col-span-full"> <div class="md:col-span-full">
<div <div
class="border-[1px] dark:border-slate-800 p-4 rounded-sm max-h-80 overflow-y-auto" class="border-[1px] border-border p-4 rounded-sm max-h-80 overflow-y-auto"
:class="model.length ? 'md:grid md:grid-cols-12 md:gap-4 md:items-center' : ''" :class="model.length ? 'md:grid md:grid-cols-12 md:gap-4 md:items-center' : ''"
> >
<div v-if="model.length" class="hidden md:block col-span-1"> <div v-if="model.length" class="hidden md:block col-span-1">
<ul class="grid gap-y-4 justify-center"> <ul class="grid gap-y-4 justify-center">
<li v-for="(m, i) in model" :key="`num-${m.name}`"> <li v-for="(m, i) in model" :key="`num-${m.name}`">
<UiButton size="icon" variant="secondary" class="h-8 w-8 rounded-full"> <UiButton size="icon" variant="secondary" class="size-8 rounded-full">
<span class="font-bold text-sm">{{ i + 1 }}</span> <span class="font-bold text-sm">{{ i + 1 }}</span>
</UiButton> </UiButton>
</li> </li>
@@ -115,7 +115,7 @@ useSortable(monthSortableList, model.value, { animation: 150, handle: ".handle"
<template v-if="model.length"> <template v-if="model.length">
<li v-for="(m, i) in model" :key="m.name" class="grid md:grid-cols-12 gap-4 md:items-center text-slate-900 bg-slate-200 rounded-md"> <li v-for="(m, i) in model" :key="m.name" class="grid md:grid-cols-12 gap-4 md:items-center text-slate-900 bg-slate-200 rounded-md">
<div class="md:col-span-1 text-right duration-200 ease-out transition transform origin-top-right"> <div class="md:col-span-1 text-right duration-200 ease-out transition transform origin-top-right">
<UiButton type="button" variant="ghost" size="icon" class="handle rounded-full h-8 w-8"> <UiButton type="button" variant="ghost" size="icon" class="handle rounded-full size-8">
<PhList size="17" /> <PhList size="17" />
</UiButton> </UiButton>
</div> </div>
@@ -145,7 +145,7 @@ useSortable(monthSortableList, model.value, { animation: 150, handle: ".handle"
<UiTooltipProvider> <UiTooltipProvider>
<UiTooltip> <UiTooltip>
<UiTooltipTrigger as-child> <UiTooltipTrigger as-child>
<UiButton type="button" variant="ghost" size="icon" class="rounded-full h-8 w-8" @click="removeMonthFromModel(i)"> <UiButton type="button" variant="ghost" size="icon" class="rounded-full size-8" @click="removeMonthFromModel(i)">
<PhTrash size="17" /> <PhTrash size="17" />
</UiButton> </UiButton>
</UiTooltipTrigger> </UiTooltipTrigger>

View File

@@ -1,46 +1,45 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useCalendar } from "@/stores/CalendarStore" import { useCalendar } from "~/stores/CalendarStore"
import { breakpointsTailwind } from "@vueuse/core"
import { PhMagnifyingGlass } from "@phosphor-icons/vue" const breakpoints = useBreakpoints(
breakpointsTailwind
)
const { revealAdvancedSearch } = useCalendar() const { isReadOnly, defaultDate } = storeToRefs(useCalendar())
const { isReadOnly } = storeToRefs(useCalendar())
</script> </script>
<template> <template>
<header class="mt-2 grid gap-4 border-slate-200 contrast-more:border-slate-500 dark:border-slate-700 border-b-[1px]"> <header class="md:mt-2 grid gap-3 md:gap-4 border-border border-b-[1px] transition-colors">
<div class="px-8 flex items-center justify-between gap-2"> <div class="px-5 md:px-8 flex items-center justify-between gap-2">
<menu class="flex items-center gap-2"> <menu class="flex items-center md:gap-2">
<li v-if="!isReadOnly">
<LazyCalendarDialogQuickCreateEvent />
</li>
<li> <li>
<CalendarMenuToday /> <LazyCalendarDialogQuickCreateEvent v-if="!isReadOnly" />
</li> </li>
<li class="ml-4"> <li class="max-md:hidden">
<LazyCalendarMenuToday v-if="defaultDate" />
</li>
<li class="ml-2 md:ml-4">
<CalendarCurrentDate /> <CalendarCurrentDate />
</li> </li>
</menu> </menu>
<menu class="flex items-center gap-2"> <menu class="flex items-center gap-2">
<li> <li>
<UiButton search-slash @click="revealAdvancedSearch()"> <CalendarSearchCTA />
<PhMagnifyingGlass size="20" weight="light" />
<span>
{{ $t('entity.advancedSearch.title') }}
</span>
</UiButton>
</li> </li>
<li> <ClientOnly>
<li v-if="breakpoints.md.value">
<CalendarCategoriesCTA /> <CalendarCategoriesCTA />
</li> </li>
</ClientOnly>
<li> <li>
<CalendarOptionsCTA /> <CalendarOptionsCTA />
</li> </li>
</menu> </menu>
</div> </div>
<div class="ml-8"> <div class="ml-4 md:ml-8">
<CalendarMenuSubnav /> <CalendarMenuSubnav />
</div> </div>
</header> </header>

View File

@@ -139,7 +139,7 @@ function toFutureFar(): void {
<template> <template>
<div class="flex gap-2"> <div class="flex gap-2">
<div class="grid items-end w-40 px-4 py-2 border-slate-200 bg-white dark:bg-black dark:border-slate-700 border-x-[1px] border-t-[1px] rounded-t-sm text-sm transition-colors"> <div class="grid items-center w-40 px-3 md:px-4 py-2 bg-white dark:bg-black border-border border-x-[1px] border-t-[1px] rounded-t-sm text-sm max-md:text-xs transition-colors">
<ClientOnly> <ClientOnly>
<span>{{ currentDate.currentDateTitle }}</span> <span>{{ currentDate.currentDateTitle }}</span>

View File

@@ -1,5 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from "vue" import { breakpointsTailwind, useBreakpoints } from "@vueuse/core"
import { PhCalendar } from "@phosphor-icons/vue"
const { defaultDate, jumpToDefaultDate, getFormattedDateTitle, currentDate } = useCalendar() const { defaultDate, jumpToDefaultDate, getFormattedDateTitle, currentDate } = useCalendar()
@@ -8,15 +9,29 @@ const defaultDateFormatted: string = getFormattedDateTitle(defaultDate, true)
const buttonDisabledState: ComputedRef<boolean> = computed<boolean>(() => { const buttonDisabledState: ComputedRef<boolean> = computed<boolean>(() => {
return currentDate.currentMonth === defaultDate.month && currentDate.currentYear === defaultDate.year return currentDate.currentMonth === defaultDate.month && currentDate.currentYear === defaultDate.year
}) })
const breakpoints = useBreakpoints(
breakpointsTailwind
)
</script> </script>
<template> <template>
<UiTooltipProvider :delay-duration="250"> <UiTooltipProvider :delay-duration="250">
<UiTooltip> <UiTooltip>
<UiTooltipTrigger as-child> <UiTooltipTrigger as-child>
<UiButton size="sm" variant="secondary" :disabled="buttonDisabledState" @click="jumpToDefaultDate"> <ClientOnly>
<UiButton
:size="breakpoints.md.value ? 'default' : 'icon'"
variant="secondary"
:disabled="buttonDisabledState" @click="jumpToDefaultDate"
>
<PhCalendar v-if="!breakpoints.md.value" size="20" weight="fill" />
<span v-if="breakpoints.md.value">
{{ $t('entity.calendar.date.today') }} {{ $t('entity.calendar.date.today') }}
</span>
</UiButton> </UiButton>
</ClientOnly>
</UiTooltipTrigger> </UiTooltipTrigger>
<UiTooltipContent> <UiTooltipContent>
<p>{{ defaultDateFormatted }}</p> <p>{{ defaultDateFormatted }}</p>

View File

@@ -2,13 +2,13 @@
import { import {
isCharacter, isCharacter,
type Character, type Character,
} from "@/models/Characters" } from "~/models/Characters"
import type { RPGDateOrder } from "@/models/Date" import type { RPGDateOrder } from "~/models/Date"
import { import {
isCalendarEvent, isCalendarEvent,
type CalendarEvent, type CalendarEvent,
} from "~/models/CalendarEvent" } from "~/models/CalendarEvent"
import { capitalize } from "@/utils/Strings" import { capitalize } from "~/utils/Strings"
import { useMagicKeys, useScroll, useStorage, whenever } from "@vueuse/core" import { useMagicKeys, useScroll, useStorage, whenever } from "@vueuse/core"
import { computed, ref, watch } from "vue" import { computed, ref, watch } from "vue"
import { searchUnifier, type SearchMode } from "../SearchMode" import { searchUnifier, type SearchMode } from "../SearchMode"
@@ -25,6 +25,7 @@ import {
import SearchList from "./lists/SearchList.vue" import SearchList from "./lists/SearchList.vue"
import type { Category } from "~/models/Category" import type { Category } from "~/models/Category"
import { cn } from "~/lib/utils"
const { isAdvancedSearchOpen, allEvents, categories } = storeToRefs(useCalendar()) const { isAdvancedSearchOpen, allEvents, categories } = storeToRefs(useCalendar())
const { characters } = storeToRefs(useCharacters()) const { characters } = storeToRefs(useCharacters())
@@ -264,10 +265,10 @@ function handleCategoryUnselect(e: Category) {
<template> <template>
<UiDialog v-model:open="isAdvancedSearchOpen" @update:open="resetSearch()"> <UiDialog v-model:open="isAdvancedSearchOpen" @update:open="resetSearch()">
<UiDialogContent <UiDialogContent
class="flex flex-col flex-nowrap top-16 -translate-y-0 data-[state=closed]:slide-out-to-top-[5%] data-[state=open]:slide-in-from-top-[5%]" class="flex flex-col flex-nowrap top-10 -translate-y-0 data-[state=closed]:slide-out-to-top-[5%] data-[state=open]:slide-in-from-top-[5%] max-md:rounded-none"
:class="{ :class="cn({
'bottom-16': searchResults.length > 0 'max-md:w-full bottom-0 md:bottom-10 top-0 md:top-10': searchResults.length > 0
}" })"
> >
<VisuallyHidden> <VisuallyHidden>
<UiDialogTitle> {{ $t('entity.advancedSearch.title') }} </UiDialogTitle> <UiDialogTitle> {{ $t('entity.advancedSearch.title') }} </UiDialogTitle>
@@ -296,7 +297,7 @@ function handleCategoryUnselect(e: Category) {
</div> </div>
<div class="flex items-center justify-between gap-8"> <div class="flex items-center justify-between gap-8">
<div> <div class="hidden">
<UiToggleGroup <UiToggleGroup
v-model="selectedEntity" v-model="selectedEntity"
type="single" type="single"
@@ -314,10 +315,16 @@ function handleCategoryUnselect(e: Category) {
</div> </div>
<div class="grow flex justify-end items-center gap-1"> <div class="grow flex justify-end items-center gap-1">
<UiTagsInput class="grow px-0 gap-y-1 w-80"> <UiTagsInput class="grow px-0 gap-y-1 md:w-80">
<div v-if="selectedCategories.length > 0" class="flex gap-2 flex-wrap items-center px-3"> <div v-if="selectedCategories.length > 0" class="flex gap-2 flex-wrap items-center px-3">
<UiTagsInputItem v-for="item in selectedCategories" :key="item.id" :value="item.name"> <UiTagsInputItem v-for="cat in selectedCategories" :key="cat.id" :value="cat.name">
<UiTagsInputItemText class="capitalize cursor-pointer" @click="handleCategoryUnselect(item)" /> <button
class="bgc px-2 capitalize cursor-pointer"
:class="cn(`element-${cat.color}`)"
@click="handleCategoryUnselect(cat)"
>
{{ capitalize(cat.name) }}
</button>
</UiTagsInputItem> </UiTagsInputItem>
</div> </div>
@@ -345,7 +352,7 @@ function handleCategoryUnselect(e: Category) {
<ComboboxPortal :to="'#searchForm'"> <ComboboxPortal :to="'#searchForm'">
<UiCommandList <UiCommandList
position="popper" position="popper"
class="w-[--radix-popper-anchor-width] rounded-md mt-2 border bg-popover text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50" class="w-(--radix-popper-anchor-width) rounded-md mt-2 border bg-popover text-popover-foreground shadow-md outline-hidden data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50"
:dismissable="true" :dismissable="true"
> >
<UiCommandEmpty /> <UiCommandEmpty />
@@ -355,8 +362,13 @@ function handleCategoryUnselect(e: Category) {
:key="cat.name" :key="cat.name"
:value="cat" :value="cat"
@select.prevent="handleCategorySelect(cat)" @select.prevent="handleCategorySelect(cat)"
>
<span
class="bgc"
:class="cn(`element-${cat.color}`)"
> >
{{ capitalize(cat.name) }} {{ capitalize(cat.name) }}
</span>
</UiCommandItem> </UiCommandItem>
</UiCommandGroup> </UiCommandGroup>
</UiCommandList> </UiCommandList>
@@ -435,7 +447,7 @@ function handleCategoryUnselect(e: Category) {
as-child as-child
> >
<UiButton <UiButton
class="w-10 h-10 p-0" class="size-8 md:size-10 p-0"
:variant="item.value === currentPage ? 'default' : 'outline'" :variant="item.value === currentPage ? 'default' : 'outline'"
> >
{{ item.value }} {{ item.value }}

View File

@@ -1,10 +1,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { Character } from "@/models/Characters" import type { Character } from "~/models/Characters"
import type { RPGDate } from "@/models/Date" import type { RPGDate } from "~/models/Date"
import { useCalendar } from "@/stores/CalendarStore" import { useCalendar } from "~/stores/CalendarStore"
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "~/components/ui/tooltip"
import { Button } from "@/components/ui/button" import { Button } from "~/components/ui/button"
import { PhArrowSquareOut, PhPlant, PhSkull } from "@phosphor-icons/vue" import { PhArrowSquareOut, PhPlant, PhSkull } from "@phosphor-icons/vue"
const props = defineProps<{ const props = defineProps<{
@@ -21,7 +21,7 @@ console.log(props.character.birth)
</script> </script>
<template> <template>
<div class="block w-full text-left py-3 px-4 border-[1px] border-slate-700 rounded-sm"> <div class="block w-full text-left py-3 px-4 border-[1px] border-border rounded-sm">
<div class="grid gap-2"> <div class="grid gap-2">
<div class="flex gap-2"> <div class="flex gap-2">
<h2 class="font-bold"> <h2 class="font-bold">
@@ -37,7 +37,7 @@ console.log(props.character.birth)
</div> </div>
</div> </div>
<menu class="flex gap-2 border-[1px] border-slate-700 rounded-sm w-fit"> <menu class="flex gap-2 border-[1px] border-border rounded-sm w-fit">
<li v-if="character.birth"> <li v-if="character.birth">
<TooltipProvider :delay-duration="100"> <TooltipProvider :delay-duration="100">
<Tooltip> <Tooltip>

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
import type { RPGDate } from "@/models/Date" import type { RPGDate } from "~/models/Date"
import type { CalendarEvent } from "@/models/CalendarEvent" import type { CalendarEvent } from "~/models/CalendarEvent"
import { PhArrowSquareOut, PhHourglassMedium, PhAlarm, PhMapPinArea, PhEye } from "@phosphor-icons/vue" import { PhArrowSquareOut, PhHourglassMedium, PhAlarm, PhMapPinArea, PhEye } from "@phosphor-icons/vue"
@@ -21,16 +21,16 @@ const dateDuration = computed<string | null>(() => props.event.endDate ? getRela
<template> <template>
<button <button
class="event group relative block w-full text-left py-3 px-4 rounded-sm transition-colors" class="event-callout group relative block w-full text-left py-2 px-3 md:py-3 md:px-4 cursor-pointer transition-colors"
:class="cn( :class="cn(
event.category ? `event-${event.category.color}` : '', event.category ? `element-${event.category.color}` : '',
{ {
'pt-4 is-hidden': event.hidden, 'pt-4 is-hidden': event.hidden,
})" })"
@click="$emit('query:date-jump', event.startDate)" @click="$emit('query:date-jump', event.startDate)"
> >
<div class="flex gap-2 items-center mb-1"> <div class="flex gap-2 items-center mb-1">
<h2 class="font-bold text-lg underline-offset-4 group-hover:underline"> <h2 class="font-semibold md:font-bold md:text-lg underline-offset-4 group-hover:underline">
{{ event.title }} {{ event.title }}
</h2> </h2>
<div v-if="event.wiki"> <div v-if="event.wiki">
@@ -43,7 +43,7 @@ const dateDuration = computed<string | null>(() => props.event.endDate ? getRela
</div> </div>
</div> </div>
<div class="flex gap-2 items-center justify-between mb-1"> <div class="flex gap-2 items-center justify-between mb-2 md:mb-1">
<template v-if="!event.endDate"> <template v-if="!event.endDate">
<p class="font-semibold text-sm opacity-75"> <p class="font-semibold text-sm opacity-75">
{{ getFormattedDateTitle(event.startDate, true) }} {{ getFormattedDateTitle(event.startDate, true) }}
@@ -81,15 +81,15 @@ const dateDuration = computed<string | null>(() => props.event.endDate ? getRela
<div class="mb-1 flex gap-x-2 items-center"> <div class="mb-1 flex gap-x-2 items-center">
<template v-if="event.location"> <template v-if="event.location">
<p class="w-fit text-sm italic opacity-75 flex items-center gap-1"> <p class="w-fit text-xs md:text-sm italic opacity-75 flex items-center gap-1">
<PhMapPinArea size="16" weight="fill" /> {{ event.location }} <PhMapPinArea size="16" weight="fill" /> {{ event.location }}
</p> </p>
</template> </template>
<p class="w-fit text-sm italic opacity-75 flex items-center gap-1"> <p class="w-fit text-xs md:text-sm italic opacity-75 flex items-center gap-1">
<PhAlarm size="16" weight="fill" /> {{ dateDifference }} <PhAlarm size="16" weight="fill" /> {{ dateDifference }}
</p> </p>
<template v-if="dateDuration"> <template v-if="dateDuration">
<p class="w-fit text-sm italic opacity-75 flex items-center gap-1"> <p class="w-fit text-xs md:text-sm italic opacity-75 flex items-center gap-1">
<PhHourglassMedium size="16" weight="fill" /> {{ $t('entity.calendar.date.while', { duration: dateDuration } )}} <PhHourglassMedium size="16" weight="fill" /> {{ $t('entity.calendar.date.while', { duration: dateDuration } )}}
</p> </p>
</template> </template>
@@ -111,8 +111,8 @@ const dateDuration = computed<string | null>(() => props.event.endDate ? getRela
</ul> </ul>
</div> </div>
<div v-if="event.description" class="text-sm"> <div v-if="event.description" class="text-xs md:text-sm">
<hr class="my-2 border-white opacity-50" > <hr class="my-3 md:my-2 border-white opacity-50" >
<span class="opacity-75"> <span class="opacity-75">
{{ event.description }} {{ event.description }}
</span> </span>

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { isCharacter, type Character } from "@/models/Characters" import { isCharacter, type Character } from "~/models/Characters"
import type { RPGDate, RPGDateOrder } from "@/models/Date" import type { RPGDate, RPGDateOrder } from "~/models/Date"
import { useCalendar } from "@/stores/CalendarStore" import { useCalendar } from "~/stores/CalendarStore"
import { computed } from "vue" import { computed } from "vue"
import { isCalendarEvent, type CalendarEvent } from "~/models/CalendarEvent" import { isCalendarEvent, type CalendarEvent } from "~/models/CalendarEvent"
import type { SearchMode } from "../../SearchMode" import type { SearchMode } from "../../SearchMode"
@@ -60,7 +60,7 @@ const pagedResults = computed(() => sortedResults.value.slice(props.startAt, pro
</script> </script>
<template> <template>
<ul class="grid gap-4 pl-8 pr-12"> <ul class="grid gap-3 md:gap-4 pl-4 pr-6 md:pl-8 md:pr-12">
<li v-for="r in pagedResults" :key="isCalendarEvent(r) ? r.title : r.name"> <li v-for="r in pagedResults" :key="isCalendarEvent(r) ? r.title : r.name">
<EventCallout v-if="isCalendarEvent(r)" :event="r" @query:date-jump="handleJumpToDate" /> <EventCallout v-if="isCalendarEvent(r)" :event="r" @query:date-jump="handleJumpToDate" />

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { RPGDate } from "@/models/Date" import type { RPGDate } from "~/models/Date"
import type { CalendarEvent } from "@/models/CalendarEvent" import type { CalendarEvent } from "~/models/CalendarEvent"
import { useElementBounding } from "@vueuse/core" import { breakpointsTailwind, useElementBounding } from "@vueuse/core"
import { storeToRefs } from "pinia" import { storeToRefs } from "pinia"
import { computed, ref, type ComputedRef } from "vue" import { computed, ref, type ComputedRef } from "vue"
@@ -12,12 +12,20 @@ const props = defineProps<{
faded?: boolean faded?: boolean
}>() }>()
const emit = defineEmits<{
(e: "on-open-create-dialog", date: RPGDate): void
}>()
const calendarTile = ref() const calendarTile = ref()
const calendarEventsList = ref() const calendarEventsList = ref()
const { defaultDate, selectDate, areDatesIdentical } = useCalendar() const { defaultDate, selectDate, areDatesIdentical, getFormattedDateTitle } = useCalendar()
const { selectedDate, currentEvents, isReadOnly } = storeToRefs(useCalendar()) const { selectedDate, currentEvents, isReadOnly } = storeToRefs(useCalendar())
const breakpoints = useBreakpoints(
breakpointsTailwind
)
/** /**
* All events with a startDate / endDate that starts or ends on the tile * All events with a startDate / endDate that starts or ends on the tile
*/ */
@@ -73,19 +81,19 @@ const eventsNotDisplayed: ComputedRef<number> = computed<number>(() => eventsFo
<template> <template>
<div <div
ref="calendarTile" ref="calendarTile"
class="tile relative text-xs p-2" class="tile relative p-1 md:p-2 transition-colors"
:class="{ :class="{
'text-slate-300 dark:text-slate-500': props.faded, 'text-slate-300 dark:text-slate-500': props.faded,
'text-slate-500 dark:text-slate-300': !props.faded 'text-slate-500 dark:text-slate-300': !props.faded
}" }"
> >
<button <button
class="relative z-10 group block w-full text-center cursor-pointer" class="relative z-10 group block w-full text-2xs md:text-xs text-center cursor-pointer"
@click="selectDate(date)" @click="selectDate(date)"
> >
<ClientOnly> <ClientOnly>
<span <span
class="inline-flex w-8 h-8 aspect-square items-center justify-center rounded-full border-2 border-transparent font-bold transition-colors group-hover:border-indigo-300 dark:group-hover:border-indigo-700" class="inline-flex size-7 md:size-8 aspect-square items-center justify-center rounded-full border-1 md:border-2 border-transparent font-bold transition-colors group-hover:border-indigo-300 dark:group-hover:border-indigo-700"
:class="{ :class="{
'text-white bg-slate-600 dark:bg-slate-800': isDefaultDate && !isSelectedDate, 'text-white bg-slate-600 dark:bg-slate-800': isDefaultDate && !isSelectedDate,
'text-white bg-indigo-500': isSelectedDate 'text-white bg-indigo-500': isSelectedDate
@@ -99,7 +107,7 @@ const eventsNotDisplayed: ComputedRef<number> = computed<number>(() => eventsFo
<ClientOnly> <ClientOnly>
<ul <ul
ref="calendarEventsList" ref="calendarEventsList"
class="absolute top-12 bottom-2 inset-x-2 grid auto-rows-min gap-1 z-10 pointer-events-none transition-opacity" class="absolute top-9 md:top-12 bottom-1 md:bottom-2 inset-x-2 grid auto-rows-min gap-1 z-10 pointer-events-none transition-opacity"
:class="{ :class="{
'opacity-40': props.faded && !isSelectedDate 'opacity-40': props.faded && !isSelectedDate
}" }"
@@ -114,17 +122,23 @@ const eventsNotDisplayed: ComputedRef<number> = computed<number>(() => eventsFo
<UiPopover> <UiPopover>
<UiPopoverTrigger as-child> <UiPopoverTrigger as-child>
<button <button
class="text-xs px-2 py-1 block w-full text-left font-bold rounded-sm whitespace-nowrap overflow-hidden text-ellipsis cursor-pointer transition-colors hover:bg-slate-800" class="text-2xs md:text-xs px-[5px] py-[5px] md:px-2 md:py-1 block w-full text-left font-bold rounded-sm whitespace-nowrap overflow-hidden text-ellipsis cursor-pointer transition-colors hover:text-foreground hover:bg-foreground/10"
> >
{{ eventsNotDisplayed }} autre{{ eventsNotDisplayed > 1 ? 's' : '' }} {{ eventsNotDisplayed }} autre{{ eventsNotDisplayed > 1 ? 's' : '' }}
</button> </button>
</UiPopoverTrigger> </UiPopoverTrigger>
<UiPopoverContent class="w-80" :align="'center'" :side="'right'"> <UiPopoverContent
<div class="text-center mb-4"> class="w-80"
<span :align="'center'"
class="inline-flex w-12 h-12 aspect-square items-center justify-center text-lg font-semibold text-slate-300 bg-slate-800 rounded-full" :align-offset="breakpoints.lg.value ? 50 : 25"
:collision-padding="breakpoints.lg.value ? 50 : 25"
:side="breakpoints.lg.value ? 'left' : 'bottom'"
> >
{{ date.day }} <div class="text-center mb-2">
<span
class="text-lg font-semibold rounded-full"
>
{{ getFormattedDateTitle(date, true) }}
</span> </span>
</div> </div>
<ul class="grid auto-rows-min gap-1 z-10 pointer-events-none transition-opacity"> <ul class="grid auto-rows-min gap-1 z-10 pointer-events-none transition-opacity">
@@ -143,14 +157,17 @@ const eventsNotDisplayed: ComputedRef<number> = computed<number>(() => eventsFo
</ClientOnly> </ClientOnly>
<ClientOnly> <ClientOnly>
<LazyCalendarDialogCreateEvent v-if="!isReadOnly" :date btn-class="absolute inset-0 w-full h-full cursor-default z-0" /> <LazyCalendarDialogCreateEvent v-if="!isReadOnly && breakpoints.lg.value" :date btn-class="absolute inset-0 w-full h-full cursor-default z-0" />
<button
v-else-if="!isReadOnly && !breakpoints.lg.value"
class="absolute inset-0 w-full h-full cursor-default z-0"
@click="emit('on-open-create-dialog', props.date)"
/>
</ClientOnly> </ClientOnly>
</div> </div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
// .tile are the direct children of the grid
// We need to get, between each element, a 1px solid line to separate them, for columns and rows
.tile { .tile {
border-right-width: 1px; border-right-width: 1px;
border-bottom-width: 1px; border-bottom-width: 1px;

View File

@@ -1,11 +1,16 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useCalendar } from "@/stores/CalendarStore" import { useCalendar } from "~/stores/CalendarStore"
import { useThrottleFn } from "@vueuse/core" import { useThrottleFn } from "@vueuse/core"
import type { RPGDate } from "~/models/Date"
import { VisuallyHidden } from "radix-vue"
const { currentDate, decrementViewMonth, incrementViewMonth } = useCalendar() const { currentDate, decrementViewMonth, incrementViewMonth, resetSkeleton } = useCalendar()
const { currentMonthData } = storeToRefs(useCalendar()) const { currentMonthData, operationInProgress, eventSkeleton } = storeToRefs(useCalendar())
function handleWheel(e: WheelEvent) { function handleWheel(e: WheelEvent) {
// Prevent scrolling
e.preventDefault()
const isMovingUp = e.deltaY < 0 const isMovingUp = e.deltaY < 0
if (isMovingUp) { if (isMovingUp) {
moveCalendarLeft() moveCalendarLeft()
@@ -21,10 +26,43 @@ const moveCalendarLeft = useThrottleFn(() => {
const moveCalendarRight = useThrottleFn(() => { const moveCalendarRight = useThrottleFn(() => {
incrementViewMonth() incrementViewMonth()
}, 100) }, 100)
const isDialogOpen = ref<boolean>(false);
/**
* Opens event creation's popover
*/
function handleDialogOpen(date: RPGDate) {
// If another operation is in progress, whether it's another create popup or a modal, don't bother opening it
if (operationInProgress.value) {
isDialogOpen.value = false
return
}
resetSkeleton()
isDialogOpen.value = true
// Set skeleton initial startDate if it's known
if (date) {
eventSkeleton.value.startDate = date
}
}
function toggleDialog() {
isDialogOpen.value = !isDialogOpen.value;
};
/**
* Prevents the modal from closing if's still loading
*/
function handleClosing() {
setTimeout(() => resetSkeleton(), 100)
}
</script> </script>
<template> <template>
<div class="grid grid-cols-10" @wheel="handleWheel"> <div class="grid grid-cols-5 md:grid-cols-7 lg:grid-cols-10" @wheel="handleWheel">
<template v-if="currentMonthData"> <template v-if="currentMonthData">
<CalendarStateMonthlyDayTile <CalendarStateMonthlyDayTile
v-for="day in currentMonthData?.days" v-for="day in currentMonthData?.days"
@@ -34,7 +72,29 @@ const moveCalendarRight = useThrottleFn(() => {
month: currentDate.currentMonth, month: currentDate.currentMonth,
year: currentDate.currentYear year: currentDate.currentYear
}" }"
@on-open-create-dialog="handleDialogOpen"
/> />
<UiDialog v-model:open="isDialogOpen">
<UiDialogContent
class="max-md:translate-0 max-md:inset-0 max-md:w-full max-md:block"
:trap-focus="true"
@escape-key-down="handleClosing"
@pointer-down-outside.prevent="null"
>
<UiDialogTitle class="max-md:mb-8">
{{ $t("entity.calendar.event.addSingle") }}
</UiDialogTitle>
<VisuallyHidden>
<UiDialogDescription>
{{ $t("entity.calendar.event.addSingleDescription") }}
</UiDialogDescription>
</VisuallyHidden>
<CalendarFormCreateEvent @event-created="toggleDialog" />
</UiDialogContent>
</UiDialog>
</template> </template>
</div> </div>
</template> </template>

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { RPGDate } from "@/models/Date" import type { RPGDate } from "~/models/Date"
import { storeToRefs } from "pinia" import { storeToRefs } from "pinia"
import { computed, type ComputedRef } from "vue" import { computed, type ComputedRef } from "vue"

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useCalendar } from "@/stores/CalendarStore" import { useCalendar } from "~/stores/CalendarStore"
import { useThrottleFn } from "@vueuse/core" import { useThrottleFn } from "@vueuse/core"
const { decrementViewYear, incrementViewYear } = useCalendar() const { decrementViewYear, incrementViewYear } = useCalendar()

View File

@@ -15,6 +15,11 @@ defineProps<{
</script> </script>
<template> <template>
<div class="flex gap-3 items-center">
<div class="md:hidden">
<SidebarToggle />
</div>
<UiBreadcrumb> <UiBreadcrumb>
<UiBreadcrumbList> <UiBreadcrumbList>
<UiBreadcrumbItem v-if="route.path !== '/my'"> <UiBreadcrumbItem v-if="route.path !== '/my'">
@@ -57,4 +62,5 @@ defineProps<{
</template> </template>
</UiBreadcrumbList> </UiBreadcrumbList>
</UiBreadcrumb> </UiBreadcrumb>
</div>
</template> </template>

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { cn } from "@/lib/utils"; import { cn } from "~/lib/utils";
import { type RPGColor, rpgColors } from "~/models/Color"; import { type RPGColor, rpgColors } from "~/models/Color";
const { id, theme = "normal", position = "popper" } = defineProps<{ const { id, theme = "normal", position = "popper" } = defineProps<{
@@ -17,7 +17,7 @@ const model = defineModel<RPGColor>({ default: "white" });
<UiSelectValue <UiSelectValue
:placeholder="$t('ui.colors.selectOne')" :placeholder="$t('ui.colors.selectOne')"
class="bgc" class="bgc"
:class="cn(`bgc-${model}`)" :class="cn(`element-${model}`)"
/> />
</UiSelectTrigger> </UiSelectTrigger>
<UiSelectContent :position> <UiSelectContent :position>
@@ -29,7 +29,7 @@ const model = defineModel<RPGColor>({ default: "white" });
class="bgc" class="bgc"
:class=" :class="
cn( cn(
`bgc-${color}`, `element-${color}`,
) )
" "
> >

View File

@@ -1,9 +1,12 @@
<script lang="ts" setup> <script lang="ts" setup>
import { PhCompass, PhGlobeHemisphereEast, PhHurricane, PhList } from "@phosphor-icons/vue" import { PhCompass, PhGlobeHemisphereEast, PhHurricane, PhX } from "@phosphor-icons/vue"
import type { SidebarMenuActionType, SidebarMenuIcon } from "./SidebarProps"; import type { SidebarMenuActionType, SidebarMenuIcon } from "./SidebarProps";
import { cn } from "~/lib/utils";
import { breakpointsTailwind } from "@vueuse/core"
const { revealAdvancedSearch } = useCalendar() const { revealAdvancedSearch } = useCalendar()
const { currentMenu } = storeToRefs(useUiStore()) const { toggleSidebar } = useUiStore()
const { currentMenu, isSidebarOpened } = storeToRefs(useUiStore())
function handleMenuItemAction(actionType: SidebarMenuActionType) { function handleMenuItemAction(actionType: SidebarMenuActionType) {
if (actionType === "event-search") { if (actionType === "event-search") {
@@ -21,26 +24,68 @@ function computeMenuItemIcon(iconString: SidebarMenuIcon) {
return PhCompass return PhCompass
} }
} }
const breakpoints = useBreakpoints(
breakpointsTailwind
)
// const sidebarRef = ref(null)
// onClickOutside(sidebarRef, () => {
// isSidebarOpened.value = false
// })
</script> </script>
<template> <template>
<nav class="w-16 py-6 border-r-[1px] bg-indigo-700 dark:bg-black text-white border-r-indigo-700 dark:border-r-indigo-950 grid grid-rows-[1fr_auto] justify-center transition-colors after:opacity-50 after:contrast-125 dark:after:opacity-75 dark:after:contrast-175 after:-hue-rotate-60"> <nav
<menu class="flex flex-col gap-4"> ref="sidebarRef"
<li class="mb-12"> :class="cn(
<UiButton variant="ghost" size="icon" class="rounded-full" @click="console.log"> ['md:relative md:isolate w-16 py-6 grid gap-4 grid-rows-[1fr_auto] justify-center transition-all'], // Base appearance
<PhList size="27" /> ['after:opacity-50 after:contrast-125 dark:after:opacity-75 dark:after:contrast-175 after:-hue-rotate-60'], // After styling
['border-r-[1px] bg-indigo-700 dark:bg-black text-white border-r-indigo-700 dark:border-r-indigo-950 shadow-navbar-light dark:shadow-navbar-dark'], // Colours
['max-md:justify-stretch max-md:px-4 max-md:py-4 max-md:absolute max-md:left-0 max-md:inset-0 max-md:z-50 max-md:w-30 max-md:max-w-full'], // Responsive behaviours
{
'max-md:-translate-x-30': !isSidebarOpened,
'max-md:-translate-x-0 shadow-navbar-dark dark:bg-slate-950': isSidebarOpened
}
)"
>
<menu class="flex flex-col gap-4 max-md:items-center">
<li class="mb-12 mt-4 max-md:self-start">
<UiButton
variant="outline"
size="icon"
class="md:hidden size-9 border-background/30"
@click="toggleSidebar"
>
<PhX size="19" />
</UiButton> </UiButton>
</li> </li>
<li> <li class="max-md:self-start">
<UiTooltipProvider :delay-duration="50"> <UiTooltipProvider :delay-duration="50" :disabled="!breakpoints.md.value">
<UiTooltip> <UiTooltip>
<UiTooltipTrigger as-child> <UiTooltipTrigger as-child>
<UiButton variant="ghost" size="icon" class="rounded-full" as-child> <UiButton
variant="ghost"
size="icon"
class="rounded-full max-md:hidden"
as-child
>
<RouterLink to="/explore"> <RouterLink to="/explore">
<PhCompass size="24" weight="fill" /> <PhCompass size="24" weight="fill" />
</RouterLink> </RouterLink>
</UiButton> </UiButton>
<RouterLink
to="/explore"
class="md:hidden flex items-center gap-[.6ch] underline-offset-4 hover:underline"
>
<PhCompass size="22" weight="fill" />
<span class="text-[.9em]">
{{ $t('pages.explore.menuLabel') }}
</span>
</RouterLink>
</UiTooltipTrigger> </UiTooltipTrigger>
<UiTooltipContent :side="'right'" :side-offset="6"> <UiTooltipContent :side="'right'" :side-offset="6">
<p> <p>
@@ -92,9 +137,6 @@ function computeMenuItemIcon(iconString: SidebarMenuIcon) {
<style lang="scss" scoped> <style lang="scss" scoped>
nav { nav {
position: relative;
isolation: isolate;
&::after { &::after {
display: block; display: block;
content: ''; content: '';

View File

@@ -0,0 +1,15 @@
<script lang="ts" setup>
import { PhList } from "@phosphor-icons/vue"
const { toggleSidebar } = useUiStore()
</script>
<template>
<UiButton
size="icon"
variant="outline"
@click="toggleSidebar"
>
<PhList size="19" weight="light" />
</UiButton>
</template>

View File

@@ -21,8 +21,7 @@ function closeMenu() {
watch(user, closeMenu) watch(user, closeMenu)
async function handleGoogleLogin() { async function handleGoogleLogin() {
try { const { error } = await auth.signInWithOAuth({
auth.signInWithOAuth({
provider: "google", provider: "google",
options: { options: {
queryParams: { queryParams: {
@@ -32,18 +31,17 @@ async function handleGoogleLogin() {
redirectTo: profileUrl redirectTo: profileUrl
} }
}) })
} catch (err) {
console.log(err) if (error) {
console.log(error.message)
} }
} }
async function handleLogout() { async function handleLogout() {
try {
const { error } = await auth.signOut() const { error } = await auth.signOut()
if (error) throw error if (error) {
} catch (err) { console.log(error.message)
console.log(err)
} }
} }

View File

@@ -1,8 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { type HTMLAttributes, computed } from "vue" import { type HTMLAttributes, computed } from "vue"
import { AlertDialogAction, type AlertDialogActionProps } from "radix-vue" import { AlertDialogAction, type AlertDialogActionProps } from "radix-vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
import { buttonVariants } from "@/components/ui/button/index" import { buttonVariants } from "~/components/ui/button/index"
const props = defineProps<AlertDialogActionProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<AlertDialogActionProps & { class?: HTMLAttributes["class"] }>()

View File

@@ -1,8 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { type HTMLAttributes, computed } from "vue" import { type HTMLAttributes, computed } from "vue"
import { AlertDialogCancel, type AlertDialogCancelProps } from "radix-vue" import { AlertDialogCancel, type AlertDialogCancelProps } from "radix-vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
import { buttonVariants } from "@/components/ui/button/index" import { buttonVariants } from "~/components/ui/button/index"
const props = defineProps<AlertDialogCancelProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<AlertDialogCancelProps & { class?: HTMLAttributes["class"] }>()

View File

@@ -8,7 +8,7 @@ import {
AlertDialogPortal, AlertDialogPortal,
useForwardPropsEmits, useForwardPropsEmits,
} from "radix-vue" } from "radix-vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps<AlertDialogContentProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<AlertDialogContentProps & { class?: HTMLAttributes["class"] }>()
const emits = defineEmits<AlertDialogContentEmits>() const emits = defineEmits<AlertDialogContentEmits>()
@@ -31,7 +31,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
v-bind="forwarded" v-bind="forwarded"
:class=" :class="
cn( cn(
'fixed left-1/2 top-1/2 z-50 grid w-full max-w-3xl -translate-x-1/2 -translate-y-1/2 gap-4 border border-indigo-200 bg-background dark:bg-slate-950 dark:border-indigo-950 p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg', 'fixed left-1/2 top-1/2 z-50 grid w-[calc(100%-2rem)] max-w-3xl -translate-x-1/2 -translate-y-1/2 gap-4 border border-indigo-200 bg-background dark:bg-slate-950 dark:border-indigo-950 p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-50% data-[state=closed]:slide-out-to-top-48% data-[state=open]:slide-in-from-left-50% data-[state=open]:slide-in-from-top-48% rounded-lg',
props.class, props.class,
) )
" "

View File

@@ -4,7 +4,7 @@ import {
AlertDialogDescription, AlertDialogDescription,
type AlertDialogDescriptionProps, type AlertDialogDescriptionProps,
} from "radix-vue" } from "radix-vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps<AlertDialogDescriptionProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<AlertDialogDescriptionProps & { class?: HTMLAttributes["class"] }>()

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes["class"] class?: HTMLAttributes["class"]

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes["class"] class?: HTMLAttributes["class"]

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { type HTMLAttributes, computed } from "vue" import { type HTMLAttributes, computed } from "vue"
import { AlertDialogTitle, type AlertDialogTitleProps } from "radix-vue" import { AlertDialogTitle, type AlertDialogTitleProps } from "radix-vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps<AlertDialogTitleProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<AlertDialogTitleProps & { class?: HTMLAttributes["class"] }>()

View File

@@ -2,7 +2,7 @@
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from "vue"
import { AvatarRoot } from "radix-vue" import { AvatarRoot } from "radix-vue"
import { type AvatarVariants, avatarVariant } from "." import { type AvatarVariants, avatarVariant } from "."
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
class?: HTMLAttributes["class"] class?: HTMLAttributes["class"]

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from "vue"
import { type BadgeVariants, badgeVariants } from "." import { type BadgeVariants, badgeVariants } from "."
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps<{ const props = defineProps<{
variant?: BadgeVariants["variant"] variant?: BadgeVariants["variant"]

View File

@@ -3,7 +3,7 @@ import { type VariantProps, cva } from "class-variance-authority"
export { default as Badge } from "./Badge.vue" export { default as Badge } from "./Badge.vue"
export const badgeVariants = cva( export const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2",
{ {
variants: { variants: {
variant: { variant: {

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
import { MoreHorizontal } from "lucide-vue-next" import { PhArrowsHorizontal } from "@phosphor-icons/vue";
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes["class"] class?: HTMLAttributes["class"]
@@ -15,7 +15,7 @@ const props = defineProps<{
:class="cn('flex h-9 w-9 items-center justify-center', props.class)" :class="cn('flex h-9 w-9 items-center justify-center', props.class)"
> >
<slot> <slot>
<MoreHorizontal class="h-4 w-4" /> <PhArrowsHorizontal class="size-4" />
</slot> </slot>
<span class="sr-only">More</span> <span class="sr-only">More</span>
</span> </span>

View File

@@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes["class"] class?: HTMLAttributes["class"]

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { PrimitiveProps } from "radix-vue" import type { PrimitiveProps } from "radix-vue"
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
import { Primitive } from "radix-vue" import { Primitive } from "radix-vue"
const props = withDefaults(defineProps<PrimitiveProps & { class?: HTMLAttributes["class"] }>(), { const props = withDefaults(defineProps<PrimitiveProps & { class?: HTMLAttributes["class"] }>(), {
@@ -13,7 +13,7 @@ const props = withDefaults(defineProps<PrimitiveProps & { class?: HTMLAttributes
<Primitive <Primitive
:as="as" :as="as"
:as-child="asChild" :as-child="asChild"
:class="cn('transition-colors hover:text-foreground', props.class)" :class="cn('transition-colors rounded-[1px] hover:text-primary focus-visible:text-primary underline-offset-4 hover:underline focus-visible:underline focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-4', props.class)"
> >
<slot /> <slot />
</Primitive> </Primitive>

View File

@@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes["class"] class?: HTMLAttributes["class"]

View File

@@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes["class"] class?: HTMLAttributes["class"]

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
import { ChevronRight } from "lucide-vue-next" import { PhCaretRight } from "@phosphor-icons/vue";
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes["class"] class?: HTMLAttributes["class"]
@@ -15,7 +15,7 @@ const props = defineProps<{
:class="cn('[&>svg]:w-3 [&>svg]:h-3', props.class)" :class="cn('[&>svg]:w-3 [&>svg]:h-3', props.class)"
> >
<slot> <slot>
<ChevronRight /> <PhCaretRight />
</slot> </slot>
</li> </li>
</template> </template>

View File

@@ -2,7 +2,7 @@
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from "vue"
import { Primitive, type PrimitiveProps } from "radix-vue" import { Primitive, type PrimitiveProps } from "radix-vue"
import { type ButtonVariants, buttonVariants } from "." import { type ButtonVariants, buttonVariants } from "."
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
interface Props extends PrimitiveProps { interface Props extends PrimitiveProps {
variant?: ButtonVariants["variant"] variant?: ButtonVariants["variant"]
@@ -20,13 +20,13 @@ const props = withDefaults(defineProps<Props>(), {
<Primitive <Primitive
:as="as" :as="as"
:as-child="asChild" :as-child="asChild"
:class="cn(buttonVariants({ variant, size }), props.class, 'group')" :class="cn(buttonVariants({ variant, size }), props.class, 'group cursor-pointer')"
> >
<slot /> <slot />
<span <span
v-if="props.searchSlash" 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-sm 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.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"
> >
<kbd class="-mt-[2px]">CTRL + :</kbd> <kbd class="-mt-[2px]">CTRL + :</kbd>
</span> </span>

View File

@@ -3,7 +3,7 @@ import { type VariantProps, cva } from "class-variance-authority"
export { default as Button } from "./Button.vue" export { default as Button } from "./Button.vue"
export const buttonVariants = cva( export const buttonVariants = cva(
"inline-flex items-center justify-center gap-1 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-30", "inline-flex items-center justify-center gap-1 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-30",
{ {
variants: { variants: {
variant: { variant: {

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes["class"] class?: HTMLAttributes["class"]
@@ -14,22 +14,23 @@ const emit = defineEmits(["on-click"])
<template> <template>
<div <div
:class=" :class="
cn('rounded-lg border bg-card text-card-foreground shadow-sm transition-all isolate', props.class, { cn('rounded-lg border bg-card text-card-foreground shadow-xs transition-all isolate', props.class, {
'relative outline outline-2 outline-offset-4 outline-transparent hover:-translate-y-[.2rem]': 'relative hover:-translate-y-[.2rem]':
props.link || props.hasClick props.link || props.hasClick
}) })
" "
> >
<slot />
<NuxtLink <NuxtLink
v-if="props.link" v-if="props.link"
:to="props.link" :to="props.link"
class="absolute inset-0 z-10 focus-visible:outline-none" class="rounded-lg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 absolute inset-0 z-10 cursor-pointer"
/> />
<button <button
v-if="props.hasClick" v-if="props.hasClick"
class="absolute inset-0 z-10 focus-visible:outline-none" class="rounded-lg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 absolute inset-0 z-10 cursor-pointer"
@click="emit('on-click')" /> @click="emit('on-click')"
/>
<slot />
</div> </div>
</template> </template>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes["class"] class?: HTMLAttributes["class"]

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes["class"] class?: HTMLAttributes["class"]

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes["class"] class?: HTMLAttributes["class"]
@@ -8,7 +8,7 @@ const props = defineProps<{
</script> </script>
<template> <template>
<div :class="cn('flex items-center p-6 border-t-2 border-t-slate-900', props.class)"> <div :class="cn('flex items-center p-6 border-t-2 border-t-border', props.class)">
<slot /> <slot />
</div> </div>
</template> </template>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes["class"] class?: HTMLAttributes["class"]

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes["class"] class?: HTMLAttributes["class"]

View File

@@ -2,8 +2,8 @@
import { type HTMLAttributes, computed } from "vue" import { type HTMLAttributes, computed } from "vue"
import type { CheckboxRootEmits, CheckboxRootProps } from "radix-vue" import type { CheckboxRootEmits, CheckboxRootProps } from "radix-vue"
import { CheckboxIndicator, CheckboxRoot, useForwardPropsEmits } from "radix-vue" import { CheckboxIndicator, CheckboxRoot, useForwardPropsEmits } from "radix-vue"
import { Check } from "lucide-vue-next" import { cn } from "~/lib/utils"
import { cn } from "@/lib/utils" import { PhCheck } from "@phosphor-icons/vue";
const props = defineProps<CheckboxRootProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<CheckboxRootProps & { class?: HTMLAttributes["class"] }>()
const emits = defineEmits<CheckboxRootEmits>() const emits = defineEmits<CheckboxRootEmits>()
@@ -21,12 +21,12 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
<CheckboxRoot <CheckboxRoot
v-bind="forwarded" v-bind="forwarded"
:class=" :class="
cn('peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground', cn('peer size-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
props.class)" props.class)"
> >
<CheckboxIndicator class="flex h-full w-full items-center justify-center text-current"> <CheckboxIndicator class="flex h-full w-full items-center justify-center text-current">
<slot> <slot>
<Check class="h-4 w-4" /> <PhCheck class="size-4" />
</slot> </slot>
</CheckboxIndicator> </CheckboxIndicator>
</CheckboxRoot> </CheckboxRoot>

View File

@@ -2,7 +2,7 @@
import { type HTMLAttributes, computed } from "vue" import { type HTMLAttributes, computed } from "vue"
import type { ComboboxRootEmits, ComboboxRootProps } from "radix-vue" import type { ComboboxRootEmits, ComboboxRootProps } from "radix-vue"
import { ComboboxRoot, useForwardPropsEmits } from "radix-vue" import { ComboboxRoot, useForwardPropsEmits } from "radix-vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = withDefaults(defineProps<ComboboxRootProps & { class?: HTMLAttributes["class"] }>(), { const props = withDefaults(defineProps<ComboboxRootProps & { class?: HTMLAttributes["class"] }>(), {
open: true, open: true,

View File

@@ -2,7 +2,7 @@
import { useForwardPropsEmits } from "radix-vue" import { useForwardPropsEmits } from "radix-vue"
import type { DialogRootEmits, DialogRootProps } from "radix-vue" import type { DialogRootEmits, DialogRootProps } from "radix-vue"
import Command from "./Command.vue" import Command from "./Command.vue"
import { Dialog, DialogContent } from "@/components/ui/dialog" import { Dialog, DialogContent } from "~/components/ui/dialog"
const props = defineProps<DialogRootProps>() const props = defineProps<DialogRootProps>()
const emits = defineEmits<DialogRootEmits>() const emits = defineEmits<DialogRootEmits>()

View File

@@ -2,7 +2,7 @@
import { type HTMLAttributes, computed } from "vue" import { type HTMLAttributes, computed } from "vue"
import type { ComboboxEmptyProps } from "radix-vue" import type { ComboboxEmptyProps } from "radix-vue"
import { ComboboxEmpty } from "radix-vue" import { ComboboxEmpty } from "radix-vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps<ComboboxEmptyProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<ComboboxEmptyProps & { class?: HTMLAttributes["class"] }>()

View File

@@ -2,7 +2,7 @@
import { type HTMLAttributes, computed } from "vue" import { type HTMLAttributes, computed } from "vue"
import type { ComboboxGroupProps } from "radix-vue" import type { ComboboxGroupProps } from "radix-vue"
import { ComboboxGroup, ComboboxLabel } from "radix-vue" import { ComboboxGroup, ComboboxLabel } from "radix-vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps< const props = defineProps<
ComboboxGroupProps & { ComboboxGroupProps & {

View File

@@ -1,8 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { type HTMLAttributes, computed } from "vue" import { type HTMLAttributes, computed } from "vue"
import { Search } from "lucide-vue-next"
import { ComboboxInput, type ComboboxInputProps, useForwardProps } from "radix-vue" import { ComboboxInput, type ComboboxInputProps, useForwardProps } from "radix-vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
import { PhMagnifyingGlass } from "@phosphor-icons/vue"
defineOptions({ defineOptions({
inheritAttrs: false inheritAttrs: false
@@ -25,13 +25,13 @@ const forwardedProps = useForwardProps(delegatedProps)
<template> <template>
<div class="flex items-center border-b px-3" cmdk-input-wrapper> <div class="flex items-center border-b px-3" cmdk-input-wrapper>
<Search class="mr-2 h-4 w-4 shrink-0 opacity-50" /> <PhMagnifyingGlass class="mr-2 size-4 shrink-0 opacity-50" />
<ComboboxInput <ComboboxInput
v-bind="{ ...forwardedProps, ...$attrs }" v-bind="{ ...forwardedProps, ...$attrs }"
auto-focus auto-focus
:class=" :class="
cn( cn(
'flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50', 'flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50',
props.class props.class
) )
" "

View File

@@ -2,7 +2,7 @@
import { type HTMLAttributes, computed } from "vue" import { type HTMLAttributes, computed } from "vue"
import type { ComboboxItemEmits, ComboboxItemProps } from "radix-vue" import type { ComboboxItemEmits, ComboboxItemProps } from "radix-vue"
import { ComboboxItem, useForwardPropsEmits } from "radix-vue" import { ComboboxItem, useForwardPropsEmits } from "radix-vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps<ComboboxItemProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<ComboboxItemProps & { class?: HTMLAttributes["class"] }>()
const emits = defineEmits<ComboboxItemEmits>() const emits = defineEmits<ComboboxItemEmits>()
@@ -21,7 +21,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
v-bind="forwarded" v-bind="forwarded"
:class=" :class="
cn( cn(
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[disabled]:pointer-events-none data-[disabled]:opacity-50', 'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-hidden data-highlighted:bg-accent data-disabled:pointer-events-none data-disabled:opacity-50',
props.class props.class
) )
" "

View File

@@ -2,7 +2,7 @@
import { type HTMLAttributes, computed } from "vue" import { type HTMLAttributes, computed } from "vue"
import type { ComboboxContentEmits, ComboboxContentProps } from "radix-vue" import type { ComboboxContentEmits, ComboboxContentProps } from "radix-vue"
import { ComboboxContent, useForwardPropsEmits } from "radix-vue" import { ComboboxContent, useForwardPropsEmits } from "radix-vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = withDefaults( const props = withDefaults(
defineProps<ComboboxContentProps & { class?: HTMLAttributes["class"] }>(), defineProps<ComboboxContentProps & { class?: HTMLAttributes["class"] }>(),

View File

@@ -2,7 +2,7 @@
import { type HTMLAttributes, computed } from "vue" import { type HTMLAttributes, computed } from "vue"
import type { ComboboxSeparatorProps } from "radix-vue" import type { ComboboxSeparatorProps } from "radix-vue"
import { ComboboxSeparator } from "radix-vue" import { ComboboxSeparator } from "radix-vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps<ComboboxSeparatorProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<ComboboxSeparatorProps & { class?: HTMLAttributes["class"] }>()

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes["class"] class?: HTMLAttributes["class"]

View File

@@ -9,8 +9,8 @@ import {
DialogPortal, DialogPortal,
useForwardPropsEmits useForwardPropsEmits
} from "radix-vue" } from "radix-vue"
import { X } from "lucide-vue-next" import { cn } from "~/lib/utils"
import { cn } from "@/lib/utils" import { PhX } from "@phosphor-icons/vue";
const props = defineProps<DialogContentProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<DialogContentProps & { class?: HTMLAttributes["class"] }>()
const emits = defineEmits<DialogContentEmits>() const emits = defineEmits<DialogContentEmits>()
@@ -33,7 +33,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
v-bind="forwarded" v-bind="forwarded"
:class=" :class="
cn( cn(
'fixed left-1/2 top-1/2 z-50 grid w-[calc(100%-2rem)] max-w-4xl -translate-x-1/2 -translate-y-1/2 gap-4 border bg-background border-indigo-200 dark:bg-slate-950 dark:border-indigo-950 p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg', 'fixed left-1/2 top-1/2 z-50 grid w-[calc(100%-2rem)] max-w-4xl -translate-x-1/2 -translate-y-1/2 gap-4 border bg-background border-indigo-200 dark:bg-slate-950 dark:border-indigo-950 p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-50% data-[state=closed]:slide-out-to-top-48% data-[state=open]:slide-in-from-left-50% data-[state=open]:slide-in-from-top-48% rounded-lg',
props.class props.class
) )
" "
@@ -41,9 +41,9 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
<slot /> <slot />
<DialogClose <DialogClose
class="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground" class="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"
> >
<X class="w-4 h-4" /> <PhX class="w-4 h-4" />
<span class="sr-only">Close</span> <span class="sr-only">Close</span>
</DialogClose> </DialogClose>
</DialogContent> </DialogContent>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { type HTMLAttributes, computed } from "vue" import { type HTMLAttributes, computed } from "vue"
import { DialogDescription, type DialogDescriptionProps, useForwardProps } from "radix-vue" import { DialogDescription, type DialogDescriptionProps, useForwardProps } from "radix-vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps<DialogDescriptionProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<DialogDescriptionProps & { class?: HTMLAttributes["class"] }>()

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>() const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script> </script>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes["class"] class?: HTMLAttributes["class"]

View File

@@ -9,8 +9,8 @@ import {
DialogPortal, DialogPortal,
useForwardPropsEmits useForwardPropsEmits
} from "radix-vue" } from "radix-vue"
import { X } from "lucide-vue-next" import { cn } from "~/lib/utils"
import { cn } from "@/lib/utils" import { PhX } from "@phosphor-icons/vue";
const props = defineProps<DialogContentProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<DialogContentProps & { class?: HTMLAttributes["class"] }>()
const emits = defineEmits<DialogContentEmits>() const emits = defineEmits<DialogContentEmits>()
@@ -32,7 +32,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
<DialogContent <DialogContent
:class=" :class="
cn( cn(
'relative z-50 grid w-full max-w-lg my-8 gap-4 border border-border bg-background p-6 shadow-lg duration-200 sm:rounded-lg md:w-full', 'relative z-50 grid w-full max-w-lg my-8 gap-4 border border-border bg-background p-6 shadow-lg duration-200 rounded-lg md:w-full',
props.class props.class
) )
" "
@@ -55,7 +55,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
<DialogClose <DialogClose
class="absolute top-3 right-3 p-0.5 transition-colors rounded-md hover:bg-secondary" class="absolute top-3 right-3 p-0.5 transition-colors rounded-md hover:bg-secondary"
> >
<X class="w-4 h-4" /> <PhX class="w-4 h-4" />
<span class="sr-only">Close</span> <span class="sr-only">Close</span>
</DialogClose> </DialogClose>
</DialogContent> </DialogContent>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { type HTMLAttributes, computed } from "vue" import { type HTMLAttributes, computed } from "vue"
import { DialogTitle, type DialogTitleProps, useForwardProps } from "radix-vue" import { DialogTitle, type DialogTitleProps, useForwardProps } from "radix-vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps<DialogTitleProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<DialogTitleProps & { class?: HTMLAttributes["class"] }>()

View File

@@ -7,8 +7,8 @@ import {
DropdownMenuItemIndicator, DropdownMenuItemIndicator,
useForwardPropsEmits useForwardPropsEmits
} from "radix-vue" } from "radix-vue"
import { Check } from "lucide-vue-next" import { cn } from "~/lib/utils"
import { cn } from "@/lib/utils" import { PhCheck } from "@phosphor-icons/vue";
const props = defineProps<DropdownMenuCheckboxItemProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<DropdownMenuCheckboxItemProps & { class?: HTMLAttributes["class"] }>()
const emits = defineEmits<DropdownMenuCheckboxItemEmits>() const emits = defineEmits<DropdownMenuCheckboxItemEmits>()
@@ -27,14 +27,14 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
v-bind="forwarded" v-bind="forwarded"
:class=" :class="
cn( cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50', 'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-hidden transition-colors focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50',
props.class props.class
) )
" "
> >
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> <span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuItemIndicator> <DropdownMenuItemIndicator>
<Check class="w-4 h-4" /> <PhCheck class="size-4" />
</DropdownMenuItemIndicator> </DropdownMenuItemIndicator>
</span> </span>
<slot /> <slot />

View File

@@ -7,7 +7,7 @@ import {
DropdownMenuPortal, DropdownMenuPortal,
useForwardPropsEmits useForwardPropsEmits
} from "radix-vue" } from "radix-vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = withDefaults( const props = withDefaults(
defineProps<DropdownMenuContentProps & { class?: HTMLAttributes["class"] }>(), defineProps<DropdownMenuContentProps & { class?: HTMLAttributes["class"] }>(),

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { type HTMLAttributes, computed } from "vue" import { type HTMLAttributes, computed } from "vue"
import { DropdownMenuItem, type DropdownMenuItemProps, useForwardProps } from "radix-vue" import { DropdownMenuItem, type DropdownMenuItemProps, useForwardProps } from "radix-vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps< const props = defineProps<
DropdownMenuItemProps & { class?: HTMLAttributes["class"]; inset?: boolean } DropdownMenuItemProps & { class?: HTMLAttributes["class"]; inset?: boolean }
@@ -21,7 +21,7 @@ const forwardedProps = useForwardProps(delegatedProps)
v-bind="forwardedProps" v-bind="forwardedProps"
:class=" :class="
cn( cn(
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50', 'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-hidden transition-colors focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50',
inset && 'pl-8', inset && 'pl-8',
props.class props.class
) )

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { type HTMLAttributes, computed } from "vue" import { type HTMLAttributes, computed } from "vue"
import { DropdownMenuLabel, type DropdownMenuLabelProps, useForwardProps } from "radix-vue" import { DropdownMenuLabel, type DropdownMenuLabelProps, useForwardProps } from "radix-vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps< const props = defineProps<
DropdownMenuLabelProps & { class?: HTMLAttributes["class"]; inset?: boolean } DropdownMenuLabelProps & { class?: HTMLAttributes["class"]; inset?: boolean }

View File

@@ -7,8 +7,8 @@ import {
type DropdownMenuRadioItemProps, type DropdownMenuRadioItemProps,
useForwardPropsEmits useForwardPropsEmits
} from "radix-vue" } from "radix-vue"
import { Circle } from "lucide-vue-next" import { cn } from "~/lib/utils"
import { cn } from "@/lib/utils" import { PhCircle } from "@phosphor-icons/vue";
const props = defineProps<DropdownMenuRadioItemProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<DropdownMenuRadioItemProps & { class?: HTMLAttributes["class"] }>()
@@ -28,14 +28,14 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
v-bind="forwarded" v-bind="forwarded"
:class=" :class="
cn( cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50', 'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-hidden transition-colors focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50',
props.class props.class
) )
" "
> >
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> <span class="absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuItemIndicator> <DropdownMenuItemIndicator>
<Circle class="h-2 w-2 fill-current" /> <PhCircle class="size-2 text-current" />
</DropdownMenuItemIndicator> </DropdownMenuItemIndicator>
</span> </span>
<slot /> <slot />

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { type HTMLAttributes, computed } from "vue" import { type HTMLAttributes, computed } from "vue"
import { DropdownMenuSeparator, type DropdownMenuSeparatorProps } from "radix-vue" import { DropdownMenuSeparator, type DropdownMenuSeparatorProps } from "radix-vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps< const props = defineProps<
DropdownMenuSeparatorProps & { DropdownMenuSeparatorProps & {

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes["class"] class?: HTMLAttributes["class"]

View File

@@ -6,7 +6,7 @@ import {
type DropdownMenuSubContentProps, type DropdownMenuSubContentProps,
useForwardPropsEmits useForwardPropsEmits
} from "radix-vue" } from "radix-vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps<DropdownMenuSubContentProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<DropdownMenuSubContentProps & { class?: HTMLAttributes["class"] }>()
const emits = defineEmits<DropdownMenuSubContentEmits>() const emits = defineEmits<DropdownMenuSubContentEmits>()

View File

@@ -5,7 +5,7 @@ import {
type DropdownMenuSubTriggerProps, type DropdownMenuSubTriggerProps,
useForwardProps useForwardProps
} from "radix-vue" } from "radix-vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
import { PhCaretLeft, PhCaretRight } from "@phosphor-icons/vue"; import { PhCaretLeft, PhCaretRight } from "@phosphor-icons/vue";
const props = defineProps<DropdownMenuSubTriggerProps & { class?: HTMLAttributes["class"], arrowDirection?: "left" | "right" }>() const props = defineProps<DropdownMenuSubTriggerProps & { class?: HTMLAttributes["class"], arrowDirection?: "left" | "right" }>()
@@ -24,13 +24,13 @@ const forwardedProps = useForwardProps(delegatedProps)
v-bind="forwardedProps" v-bind="forwardedProps"
:class=" :class="
cn( cn(
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent', 'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-hidden focus:bg-accent data-[state=open]:bg-accent',
props.class props.class
) )
" "
> >
<template v-if="props.arrowDirection === 'left'"> <template v-if="props.arrowDirection === 'left'">
<PhCaretLeft class="mr-auto h-4 w-4" /> <PhCaretLeft class="mr-auto size-4" />
<span class="inline-block"> <span class="inline-block">
<slot /> <slot />
@@ -39,7 +39,7 @@ const forwardedProps = useForwardProps(delegatedProps)
<template v-else> <template v-else>
<slot /> <slot />
<PhCaretRight class="ml-auto h-4 w-4" /> <PhCaretRight class="ml-auto size-4" />
</template> </template>
</DropdownMenuSubTrigger> </DropdownMenuSubTrigger>
</template> </template>

View File

@@ -7,7 +7,7 @@ const forwardedProps = useForwardProps(props)
</script> </script>
<template> <template>
<DropdownMenuTrigger class="outline-none" v-bind="forwardedProps"> <DropdownMenuTrigger class="outline-hidden" v-bind="forwardedProps">
<slot /> <slot />
</DropdownMenuTrigger> </DropdownMenuTrigger>
</template> </template>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from "vue"
import { useVModel } from "@vueuse/core" import { useVModel } from "@vueuse/core"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps<{ const props = defineProps<{
defaultValue?: string | number defaultValue?: string | number
@@ -24,7 +24,7 @@ const modelValue = useVModel(props, "modelValue", emits, {
v-model="modelValue" v-model="modelValue"
:class=" :class="
cn( cn(
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', 'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
props.class props.class
) )
" "

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { type HTMLAttributes, computed } from "vue" import { type HTMLAttributes, computed } from "vue"
import { Label, type LabelProps } from "radix-vue" import { Label, type LabelProps } from "radix-vue"
import { cn } from "@/lib/utils" import { cn } from "~/lib/utils"
const props = defineProps<LabelProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<LabelProps & { class?: HTMLAttributes["class"] }>()

View File

@@ -1,8 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { type HTMLAttributes, computed } from "vue" import { type HTMLAttributes, computed } from "vue"
import { PaginationEllipsis, type PaginationEllipsisProps } from "radix-vue" import { PaginationEllipsis, type PaginationEllipsisProps } from "radix-vue"
import { MoreHorizontal } from "lucide-vue-next" import { cn } from "~/lib/utils"
import { cn } from "@/lib/utils" import { PhArrowsHorizontal } from "@phosphor-icons/vue";
const props = defineProps<PaginationEllipsisProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<PaginationEllipsisProps & { class?: HTMLAttributes["class"] }>()
@@ -16,10 +16,10 @@ const delegatedProps = computed(() => {
<template> <template>
<PaginationEllipsis <PaginationEllipsis
v-bind="delegatedProps" v-bind="delegatedProps"
:class="cn('w-9 h-9 flex items-center justify-center', props.class)" :class="cn('size-8 md:size-10 flex items-center justify-center', props.class)"
> >
<slot> <slot>
<MoreHorizontal /> <PhArrowsHorizontal />
</slot> </slot>
</PaginationEllipsis> </PaginationEllipsis>
</template> </template>

Some files were not shown because too many files have changed in this diff Show More