Compare commits
89 Commits
update
...
upgrade/ta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
015d0c4db4 | ||
|
|
392d8cd8ec | ||
|
|
473c69c9a1 | ||
|
|
790deeec5d | ||
|
|
687b96cf12 | ||
|
|
3e973dd0e9 | ||
|
|
cc07f898ea | ||
|
|
e5bc5c8a41 | ||
|
|
2faefa32ef | ||
|
|
d681c45a1e | ||
|
|
db6ae36743 | ||
|
|
6abf89e8db | ||
|
|
f1abae1cce | ||
|
|
a50824bd4f | ||
|
|
ed1252add4 | ||
|
|
1e0d840b41 | ||
|
|
58f3783b13 | ||
|
|
c25c10f7b7 | ||
|
|
f00e049f76 | ||
|
|
02fba36421 | ||
|
|
aa76b17c37 | ||
|
|
b32fab81f6 | ||
|
|
8005a8e9a2 | ||
|
|
d4d74db5d9 | ||
|
|
637a5cd7e3 | ||
|
|
2856185a3c | ||
|
|
d3a1ff92e2 | ||
|
|
d2295024bd | ||
|
|
149d0794e3 | ||
|
|
7f3a2b8d56 | ||
|
|
6992bca957 | ||
|
|
f3fd97e3cd | ||
|
|
e6b82e9b85 | ||
|
|
15458ab5fd | ||
|
|
6add25f45f | ||
|
|
1bee00ffd6 | ||
|
|
404174de31 | ||
|
|
5b38b3d0fa | ||
|
|
586adee6a9 | ||
|
|
f94c688baa | ||
|
|
9034406a45 | ||
|
|
9e814e319f | ||
|
|
a1dc761904 | ||
|
|
c3c4554e09 | ||
|
|
c249dc8334 | ||
|
|
895d8a887b | ||
|
|
0126d42762 | ||
|
|
1de5f13df6 | ||
|
|
f0e3fc2167 | ||
|
|
fdb77a20f3 | ||
|
|
2cdf793e67 | ||
|
|
d7b299e0eb | ||
|
|
04ecbe1c2b | ||
|
|
267ed70f5a | ||
|
|
2716e6d559 | ||
|
|
5664204fce | ||
|
|
e0d5f679b0 | ||
|
|
ae8937e7a6 | ||
|
|
655f5d20f3 | ||
|
|
965452671b | ||
|
|
31a4316186 | ||
|
|
d46da12745 | ||
|
|
1aa3350da3 | ||
|
|
8848fa75b1 | ||
|
|
282aabb7fa | ||
|
|
204de011f6 | ||
|
|
d73fe93812 | ||
|
|
5df57ee587 | ||
|
|
3f4cb46ebf | ||
|
|
922bcdc64b | ||
|
|
a83bc240b1 | ||
|
|
7e2989e383 | ||
|
|
71866b0e4b | ||
|
|
ba3eb29e04 | ||
|
|
189f0eec75 | ||
|
|
4dbd80fa80 | ||
|
|
b2ec583a09 | ||
|
|
d663253a05 | ||
|
|
0cc6d0f1ee | ||
|
|
9cc0eabe3c | ||
|
|
2e63836b80 | ||
|
|
fe035da7c3 | ||
|
|
04940aacc3 | ||
|
|
d04cee2d12 | ||
|
|
879ff732ef | ||
|
|
500941ae69 | ||
|
|
d6d173d087 | ||
|
|
c59d4eaf92 | ||
|
|
60755fd46f |
301
assets/_colors.css
Normal file
301
assets/_colors.css
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
: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 {
|
||||||
|
--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-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(--bg-color);
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
295
assets/main.css
295
assets/main.css
@@ -1,279 +1,32 @@
|
|||||||
@tailwind base;
|
@import 'tailwindcss';
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
|
@config '../tailwind.config.js';
|
||||||
|
|
||||||
|
@import "./theme.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 40% 96.1%;
|
|
||||||
--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 {
|
body {
|
||||||
* {
|
color: var(--color-foreground);
|
||||||
@apply border-border;
|
background-color: var(--color-background);
|
||||||
}
|
|
||||||
body {
|
|
||||||
@apply bg-background text-foreground;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Colors */
|
|
||||||
.event-red {
|
|
||||||
@apply text-slate-900 bg-red-500 hover:bg-red-600
|
|
||||||
}
|
|
||||||
.event-orange {
|
|
||||||
@apply text-slate-900 bg-orange-500 hover:bg-orange-600
|
|
||||||
}
|
|
||||||
.event-amber {
|
|
||||||
@apply text-slate-900 bg-amber-500 hover:bg-amber-600
|
|
||||||
}
|
|
||||||
.event-yellow {
|
|
||||||
@apply text-slate-900 bg-yellow-600 hover:bg-yellow-700
|
|
||||||
}
|
|
||||||
.event-lime {
|
|
||||||
@apply text-slate-900 bg-lime-500 hover:bg-lime-600
|
|
||||||
}
|
|
||||||
.event-green {
|
|
||||||
@apply text-white bg-green-600 hover:bg-green-700
|
|
||||||
}
|
|
||||||
.event-emerald {
|
|
||||||
@apply text-white bg-emerald-600 hover:bg-emerald-700
|
|
||||||
}
|
|
||||||
.event-teal {
|
|
||||||
@apply text-white bg-teal-600 hover:bg-teal-700
|
|
||||||
}
|
|
||||||
.event-cyan {
|
|
||||||
@apply text-white bg-cyan-600 hover:bg-cyan-700
|
|
||||||
}
|
|
||||||
.event-sky {
|
|
||||||
@apply text-white bg-sky-600 hover:bg-sky-700
|
|
||||||
}
|
|
||||||
.event-blue {
|
|
||||||
@apply text-white bg-blue-600 hover:bg-blue-700
|
|
||||||
}
|
|
||||||
.event-indigo {
|
|
||||||
@apply text-white bg-indigo-600 hover:bg-indigo-700
|
|
||||||
}
|
|
||||||
.event-violet {
|
|
||||||
@apply text-white bg-violet-600 hover:bg-violet-700
|
|
||||||
}
|
|
||||||
.event-purple {
|
|
||||||
@apply text-white bg-purple-600 hover:bg-purple-700
|
|
||||||
}
|
|
||||||
.event-fuchsia {
|
|
||||||
@apply text-white bg-fuchsia-600 hover:bg-fuchsia-700
|
|
||||||
}
|
|
||||||
.event-pink {
|
|
||||||
@apply text-white bg-pink-700 hover:bg-pink-800
|
|
||||||
}
|
|
||||||
.event-rose {
|
|
||||||
@apply text-white bg-rose-700 hover:bg-rose-800
|
|
||||||
}
|
|
||||||
.event-black {
|
|
||||||
@apply text-white bg-stone-500 hover:bg-stone-700
|
|
||||||
}
|
|
||||||
.event-white {
|
|
||||||
@apply text-slate-900 bg-white hover:bg-yellow-200 border-[1px] border-slate-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%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fade-enter-active,
|
.fade-enter-active,
|
||||||
|
|||||||
108
assets/theme.css
Normal file
108
assets/theme.css
Normal 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(244 47% 20%);
|
||||||
|
--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);
|
||||||
|
}
|
||||||
@@ -8,17 +8,6 @@ import CenturyLayout from "./state/centennially/Layout.vue"
|
|||||||
import DecadeLayout from "./state/decennially/Layout.vue"
|
import DecadeLayout from "./state/decennially/Layout.vue"
|
||||||
import YearLayout from "./state/yearly/Layout.vue"
|
import YearLayout from "./state/yearly/Layout.vue"
|
||||||
|
|
||||||
import type { Calendar } from "~/models/CalendarConfig"
|
|
||||||
import type { Category } from "~/models/Category"
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
calendarData: Calendar,
|
|
||||||
categories: Category[]
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const { setActiveCalendar } = useCalendar()
|
|
||||||
setActiveCalendar(props.calendarData, props.categories)
|
|
||||||
|
|
||||||
const { currentConfig, jumpToDate, selectedDate } = useCalendar()
|
const { currentConfig, jumpToDate, selectedDate } = useCalendar()
|
||||||
const { isReadOnly } = storeToRefs(useCalendar())
|
const { isReadOnly } = storeToRefs(useCalendar())
|
||||||
|
|
||||||
@@ -46,14 +35,15 @@ 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" />
|
||||||
|
|
||||||
<CalendarSearch />
|
<LazyCalendarSearch />
|
||||||
<CalendarDialogUpdateEvent v-if="!isReadOnly" />
|
<LazyCalendarDialogCategories v-if="!isReadOnly" />
|
||||||
<CalendarDialogDeleteEvent v-if="!isReadOnly" />
|
<LazyCalendarDialogUpdateEvent v-if="!isReadOnly" />
|
||||||
|
<LazyCalendarDialogDeleteEvent v-if="!isReadOnly" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,200 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { computed, type ComputedRef } from "vue"
|
|
||||||
import { useCalendar } from "@/stores/CalendarStore"
|
|
||||||
|
|
||||||
import {
|
|
||||||
PhCaretDoubleLeft,
|
|
||||||
PhCaretDoubleRight,
|
|
||||||
PhCaretLeft,
|
|
||||||
PhCaretRight
|
|
||||||
} from "@phosphor-icons/vue"
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
interface DirectionLabels {
|
|
||||||
pastFar: string
|
|
||||||
pastNear: string
|
|
||||||
futureNear: string
|
|
||||||
futureFar: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const { currentConfig, decrementViewMonth, incrementViewMonth, decrementViewYear, incrementViewYear } =
|
|
||||||
useCalendar()
|
|
||||||
|
|
||||||
const activeDirectionLabels: ComputedRef<DirectionLabels> = computed(() => {
|
|
||||||
switch (currentConfig.viewType) {
|
|
||||||
case "month":
|
|
||||||
return {
|
|
||||||
pastFar: t("entity.calendar.years.prevSingular"),
|
|
||||||
pastNear: t("entity.calendar.months.prevSingular"),
|
|
||||||
futureNear: t("entity.calendar.months.nextSingular"),
|
|
||||||
futureFar: t("entity.calendar.years.nextSingular")
|
|
||||||
}
|
|
||||||
|
|
||||||
case "year":
|
|
||||||
return {
|
|
||||||
pastFar: t("entity.calendar.decades.prevSingular"),
|
|
||||||
pastNear: t("entity.calendar.years.prevSingular"),
|
|
||||||
futureNear: t("entity.calendar.years.nextSingular"),
|
|
||||||
futureFar: t("entity.calendar.decades.nextSingular")
|
|
||||||
}
|
|
||||||
|
|
||||||
case "decade":
|
|
||||||
return {
|
|
||||||
pastFar: t("entity.calendar.centuries.prevSingular"),
|
|
||||||
pastNear: t("entity.calendar.decades.prevSingular"),
|
|
||||||
futureNear: t("entity.calendar.decades.nextSingular"),
|
|
||||||
futureFar: t("entity.calendar.centuries.nextSingular")
|
|
||||||
}
|
|
||||||
|
|
||||||
case "century":
|
|
||||||
default:
|
|
||||||
return {
|
|
||||||
pastFar: t("entity.calendar.millenias.prevSingular"),
|
|
||||||
pastNear: t("entity.calendar.centuries.prevSingular"),
|
|
||||||
futureNear: t("entity.calendar.centuries.nextSingular"),
|
|
||||||
futureFar: t("entity.calendar.millenias.nextSingular")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function toPastFar(): void {
|
|
||||||
switch (currentConfig.viewType) {
|
|
||||||
case "month":
|
|
||||||
decrementViewYear()
|
|
||||||
break
|
|
||||||
|
|
||||||
case "year":
|
|
||||||
decrementViewYear(10)
|
|
||||||
break
|
|
||||||
|
|
||||||
case "decade":
|
|
||||||
decrementViewYear(100)
|
|
||||||
break
|
|
||||||
|
|
||||||
case "century":
|
|
||||||
default:
|
|
||||||
decrementViewYear(1000)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toPastNear(): void {
|
|
||||||
switch (currentConfig.viewType) {
|
|
||||||
case "month":
|
|
||||||
decrementViewMonth()
|
|
||||||
break
|
|
||||||
|
|
||||||
case "year":
|
|
||||||
decrementViewYear()
|
|
||||||
break
|
|
||||||
|
|
||||||
case "decade":
|
|
||||||
decrementViewYear(10)
|
|
||||||
break
|
|
||||||
|
|
||||||
case "century":
|
|
||||||
default:
|
|
||||||
decrementViewYear(100)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toFutureNear(): void {
|
|
||||||
switch (currentConfig.viewType) {
|
|
||||||
case "month":
|
|
||||||
incrementViewMonth()
|
|
||||||
break
|
|
||||||
|
|
||||||
case "year":
|
|
||||||
incrementViewYear()
|
|
||||||
break
|
|
||||||
|
|
||||||
case "decade":
|
|
||||||
incrementViewYear(10)
|
|
||||||
break
|
|
||||||
|
|
||||||
case "century":
|
|
||||||
default:
|
|
||||||
incrementViewYear(100)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toFutureFar(): void {
|
|
||||||
switch (currentConfig.viewType) {
|
|
||||||
case "month":
|
|
||||||
incrementViewYear()
|
|
||||||
break
|
|
||||||
|
|
||||||
case "year":
|
|
||||||
incrementViewYear(10)
|
|
||||||
break
|
|
||||||
|
|
||||||
case "decade":
|
|
||||||
incrementViewYear(100)
|
|
||||||
break
|
|
||||||
|
|
||||||
case "century":
|
|
||||||
default:
|
|
||||||
incrementViewYear(1000)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<UiTooltipProvider :delay-duration="250">
|
|
||||||
<UiTooltip>
|
|
||||||
<UiTooltipTrigger as-child>
|
|
||||||
<UiButton variant="outline" size="icon" @click="toPastFar()">
|
|
||||||
<PhCaretDoubleLeft size="18" />
|
|
||||||
</UiButton>
|
|
||||||
</UiTooltipTrigger>
|
|
||||||
<UiTooltipContent>
|
|
||||||
<p>{{ activeDirectionLabels.pastFar }}</p>
|
|
||||||
</UiTooltipContent>
|
|
||||||
</UiTooltip>
|
|
||||||
</UiTooltipProvider>
|
|
||||||
|
|
||||||
<UiTooltipProvider :delay-duration="250">
|
|
||||||
<UiTooltip>
|
|
||||||
<UiTooltipTrigger as-child>
|
|
||||||
<UiButton variant="outline" size="icon" @click="toPastNear()">
|
|
||||||
<PhCaretLeft size="18" />
|
|
||||||
</UiButton>
|
|
||||||
</UiTooltipTrigger>
|
|
||||||
<UiTooltipContent>
|
|
||||||
<p>{{ activeDirectionLabels.pastNear }}</p>
|
|
||||||
</UiTooltipContent>
|
|
||||||
</UiTooltip>
|
|
||||||
</UiTooltipProvider>
|
|
||||||
|
|
||||||
<UiTooltipProvider :delay-duration="250">
|
|
||||||
<UiTooltip>
|
|
||||||
<UiTooltipTrigger as-child>
|
|
||||||
<UiButton variant="outline" size="icon" @click="toFutureNear()">
|
|
||||||
<PhCaretRight size="18" />
|
|
||||||
</UiButton>
|
|
||||||
</UiTooltipTrigger>
|
|
||||||
<UiTooltipContent>
|
|
||||||
<p>{{ activeDirectionLabels.futureNear }}</p>
|
|
||||||
</UiTooltipContent>
|
|
||||||
</UiTooltip>
|
|
||||||
</UiTooltipProvider>
|
|
||||||
|
|
||||||
<UiTooltipProvider :delay-duration="250">
|
|
||||||
<UiTooltip>
|
|
||||||
<UiTooltipTrigger as-child>
|
|
||||||
<UiButton variant="outline" size="icon" @click="toFutureFar()">
|
|
||||||
<PhCaretDoubleRight size="18" />
|
|
||||||
</UiButton>
|
|
||||||
</UiTooltipTrigger>
|
|
||||||
<UiTooltipContent>
|
|
||||||
<p>{{ activeDirectionLabels.futureFar }}</p>
|
|
||||||
</UiTooltipContent>
|
|
||||||
</UiTooltip>
|
|
||||||
</UiTooltipProvider>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
const { currentDate } = useCalendar()
|
|
||||||
|
|
||||||
// const { toast } = useToast()
|
|
||||||
// const { t } = useI18n()
|
|
||||||
|
|
||||||
// function handleGotoPreviousEventPage(position: "next" | "prev" = "next") {
|
|
||||||
// let fromDate: RPGDate
|
|
||||||
|
|
||||||
// // To modify, obviously
|
|
||||||
// const daysPerMonth = 32
|
|
||||||
|
|
||||||
// const toDay = position === "next" ? daysPerMonth : 1
|
|
||||||
// const toMonth = position === "next" ? monthsPerYear : 0
|
|
||||||
|
|
||||||
// switch (currentConfig.viewType) {
|
|
||||||
// case "month":
|
|
||||||
// fromDate = {
|
|
||||||
// day: toDay,
|
|
||||||
// month: currentDate.currentMonth,
|
|
||||||
// year: currentDate.currentYear
|
|
||||||
// }
|
|
||||||
// break
|
|
||||||
|
|
||||||
// case "year":
|
|
||||||
// fromDate = {
|
|
||||||
// day: toDay,
|
|
||||||
// month: toMonth,
|
|
||||||
// year: currentDate.currentYear
|
|
||||||
// }
|
|
||||||
// break
|
|
||||||
|
|
||||||
// case "decade":
|
|
||||||
// fromDate = {
|
|
||||||
// day: toDay,
|
|
||||||
// month: currentDate.currentMonth,
|
|
||||||
// year: currentDate.currentYear
|
|
||||||
// }
|
|
||||||
// break
|
|
||||||
|
|
||||||
// case "century":
|
|
||||||
// default:
|
|
||||||
// fromDate = {
|
|
||||||
// day: toDay,
|
|
||||||
// month: currentDate.currentMonth,
|
|
||||||
// year: currentDate.currentYear
|
|
||||||
// }
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
|
|
||||||
// try {
|
|
||||||
// const { targetDate } = getRelativeEventFromDate(fromDate, position)
|
|
||||||
|
|
||||||
// jumpToDate(targetDate)
|
|
||||||
// } catch (err) {
|
|
||||||
// toast({
|
|
||||||
// title: t("entity.calendar.event.outOfBoundsTitle"),
|
|
||||||
// variant: "default",
|
|
||||||
// description: t("entity.calendar.event.outOfBoundsMessage"),
|
|
||||||
// duration: 4000,
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<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">
|
|
||||||
<ClientOnly>
|
|
||||||
<span>{{ currentDate.currentDateTitle }}</span>
|
|
||||||
|
|
||||||
<template #fallback>
|
|
||||||
<span class="inline-block">
|
|
||||||
<UiSkeleton class="h-[19px] w-full rounded-sm" />
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</ClientOnly>
|
|
||||||
</div>
|
|
||||||
<!-- <div>
|
|
||||||
<UiTooltipProvider :delay-duration="250">
|
|
||||||
<UiTooltip>
|
|
||||||
<UiTooltipTrigger as-child>
|
|
||||||
<UiButton
|
|
||||||
variant="outline"
|
|
||||||
size="icon"
|
|
||||||
class="rounded-t-sm rounded-b-none border-b-0"
|
|
||||||
@click="handleGotoPreviousEventPage('prev')"
|
|
||||||
>
|
|
||||||
<PhArrowLineLeft size="22" />
|
|
||||||
</UiButton>
|
|
||||||
</UiTooltipTrigger>
|
|
||||||
<UiTooltipContent>
|
|
||||||
<p>
|
|
||||||
{{ $t('entity.calendar.event.prevPage') }}
|
|
||||||
</p>
|
|
||||||
</UiTooltipContent>
|
|
||||||
</UiTooltip>
|
|
||||||
</UiTooltipProvider>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<UiTooltipProvider :delay-duration="250">
|
|
||||||
<UiTooltip>
|
|
||||||
<UiTooltipTrigger as-child>
|
|
||||||
<UiButton
|
|
||||||
variant="outline"
|
|
||||||
size="icon"
|
|
||||||
class="rounded-t-sm rounded-b-none border-b-0"
|
|
||||||
@click="handleGotoPreviousEventPage('next')"
|
|
||||||
>
|
|
||||||
<PhArrowLineRight size="22" />
|
|
||||||
</UiButton>
|
|
||||||
</UiTooltipTrigger>
|
|
||||||
<UiTooltipContent>
|
|
||||||
<p>
|
|
||||||
{{ $t('entity.calendar.event.nextPage') }}
|
|
||||||
</p>
|
|
||||||
</UiTooltipContent>
|
|
||||||
</UiTooltip>
|
|
||||||
</UiTooltipProvider>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { useCalendar } from "@/stores/CalendarStore"
|
|
||||||
import { PhCalendarBlank } from "@phosphor-icons/vue"
|
|
||||||
import { computed } from "vue"
|
|
||||||
|
|
||||||
const { currentConfig, viewTypeOptions, getViewTypeTitle, setViewType } = useCalendar()
|
|
||||||
|
|
||||||
const viewTypeTitle = computed(() => getViewTypeTitle(currentConfig.viewType))
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<UiDropdownMenu>
|
|
||||||
<UiDropdownMenuTrigger as-child>
|
|
||||||
<UiButton size="sm" variant="secondary">
|
|
||||||
<PhCalendarBlank size="18" weight="fill" />
|
|
||||||
|
|
||||||
{{ viewTypeTitle }}
|
|
||||||
</UiButton>
|
|
||||||
</UiDropdownMenuTrigger>
|
|
||||||
<UiDropdownMenuContent :side="'bottom'" :collision-padding="30">
|
|
||||||
<UiDropdownMenuLabel>
|
|
||||||
{{ $t('ui.displayMode') }}
|
|
||||||
</UiDropdownMenuLabel>
|
|
||||||
<UiDropdownMenuSeparator />
|
|
||||||
<UiDropdownMenuItem
|
|
||||||
v-for="option in viewTypeOptions"
|
|
||||||
:key="option"
|
|
||||||
@click="setViewType(option)"
|
|
||||||
>
|
|
||||||
{{ getViewTypeTitle(option) }}
|
|
||||||
</UiDropdownMenuItem>
|
|
||||||
</UiDropdownMenuContent>
|
|
||||||
</UiDropdownMenu>
|
|
||||||
</template>
|
|
||||||
23
components/calendar/CategoriesCTA.vue
Normal file
23
components/calendar/CategoriesCTA.vue
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { PhTag } from "@phosphor-icons/vue"
|
||||||
|
|
||||||
|
const { toggleCategoriesModal } = useCalendar()
|
||||||
|
const { isReadOnly } = storeToRefs(useCalendar())
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UiTooltipProvider :delay-duration="250">
|
||||||
|
<UiTooltip>
|
||||||
|
<UiTooltipTrigger as-child>
|
||||||
|
<UiButton v-if="!isReadOnly" variant="secondary" size="icon" @click="toggleCategoriesModal(true)">
|
||||||
|
<PhTag size="20" weight="light" />
|
||||||
|
</UiButton>
|
||||||
|
</UiTooltipTrigger>
|
||||||
|
<UiTooltipContent>
|
||||||
|
<p>
|
||||||
|
{{ $t('entity.calendar.seeCategories') }}
|
||||||
|
</p>
|
||||||
|
</UiTooltipContent>
|
||||||
|
</UiTooltip>
|
||||||
|
</UiTooltipProvider>
|
||||||
|
</template>
|
||||||
@@ -4,26 +4,29 @@ import { computed } from "vue"
|
|||||||
|
|
||||||
import { PhMapPin } from "@phosphor-icons/vue"
|
import { PhMapPin } from "@phosphor-icons/vue"
|
||||||
|
|
||||||
const { defaultDate, getFormattedDateTitle, getRelativeString } = useCalendar()
|
const { defaultDate, getFormattedDateTitle, getRelativeString, getDifferenceInDays } = useCalendar()
|
||||||
const { selectedDate } = storeToRefs(useCalendar())
|
const { selectedDate } = storeToRefs(useCalendar())
|
||||||
|
|
||||||
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))
|
||||||
|
|
||||||
const dateDifference = computed(() => getRelativeString(defaultDate, selectedDate.value))
|
const dateDifference = computed(() => getRelativeString(defaultDate, selectedDate.value))
|
||||||
|
const isToday = computed(() => getDifferenceInDays(defaultDate, selectedDate.value) === 0)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<h1 class="text-2xl font-bold flex items-center gap-1">
|
<div class="flex gap-2 items-center">
|
||||||
<PhMapPin size="26" weight="bold" /> {{ mainDateTitle }}
|
<h1 class="text-2xl font-bold flex items-center gap-1">
|
||||||
</h1>
|
<PhMapPin size="26" weight="bold" /> {{ mainDateTitle }}
|
||||||
<h2 class="text-lg italic opacity-75">
|
</h1>
|
||||||
{{ dateDifference }}
|
<h2 v-if="!isToday" class="text-xl italic opacity-75">
|
||||||
</h2>
|
– {{ dateDifference }}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
<template #fallback>
|
<template #fallback>
|
||||||
<div class="grid gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<UiSkeleton class="h-8 w-64" />
|
<UiSkeleton class="h-8 w-64" />
|
||||||
<UiSkeleton class="h-6 w-28" />
|
<UiSkeleton class="h-6 w-28" />
|
||||||
</div>
|
</div>
|
||||||
50
components/calendar/OptionsCTA.vue
Normal file
50
components/calendar/OptionsCTA.vue
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { useCalendar } from "@/stores/CalendarStore"
|
||||||
|
import { PhCalendarBlank, PhCheckCircle, PhGear } from "@phosphor-icons/vue"
|
||||||
|
import { computed } from "vue"
|
||||||
|
|
||||||
|
const { currentConfig, viewTypeOptions, getViewTypeTitle, setViewType } = useCalendar()
|
||||||
|
const viewTypeTitle = computed(() => getViewTypeTitle(currentConfig.viewType))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UiDropdownMenu>
|
||||||
|
<UiDropdownMenuTrigger as-child>
|
||||||
|
<UiButton variant="secondary" size="icon">
|
||||||
|
<PhGear size="20" weight="fill" />
|
||||||
|
</UiButton>
|
||||||
|
</UiDropdownMenuTrigger>
|
||||||
|
|
||||||
|
<UiDropdownMenuContent :side="'bottom'" :align="'start'" :side-offset="10" :align-offset="25" :collision-padding="40" class="text-right">
|
||||||
|
<UiDropdownMenuArrow />
|
||||||
|
<UiDropdownMenuLabel>
|
||||||
|
{{ $t('entity.calendar.seeOptions') }}
|
||||||
|
</UiDropdownMenuLabel>
|
||||||
|
<UiDropdownMenuSub>
|
||||||
|
<UiDropdownMenuSubTrigger arrow-direction="left" class="p-0 rounded-none">
|
||||||
|
<UiDropdownMenuItem class="flex gap-[1ch] justify-end items-center pointer-events-none">
|
||||||
|
{{ $t('ui.displayMode') }}
|
||||||
|
|
||||||
|
<PhCalendarBlank size="18" weight="fill" />
|
||||||
|
</UiDropdownMenuItem>
|
||||||
|
</UiDropdownMenuSubTrigger>
|
||||||
|
<UiDropdownMenuPortal>
|
||||||
|
<UiDropdownMenuSubContent>
|
||||||
|
<UiDropdownMenuItem
|
||||||
|
v-for="option in viewTypeOptions"
|
||||||
|
:key="option"
|
||||||
|
class="flex gap-[.5ch] items-center rounded-none transition-colors"
|
||||||
|
:class="cn({ 'text-emerald-600': viewTypeTitle === getViewTypeTitle(option) })"
|
||||||
|
@click="setViewType(option)"
|
||||||
|
>
|
||||||
|
<PhCheckCircle v-if="viewTypeTitle === getViewTypeTitle(option)" size="20" weight="fill" />
|
||||||
|
|
||||||
|
{{ getViewTypeTitle(option) }}
|
||||||
|
</UiDropdownMenuItem>
|
||||||
|
</UiDropdownMenuSubContent>
|
||||||
|
</UiDropdownMenuPortal>
|
||||||
|
</UiDropdownMenuSub>
|
||||||
|
</UiDropdownMenuContent>
|
||||||
|
</UiDropdownMenu>
|
||||||
|
</template>
|
||||||
102
components/calendar/PreviewCard.vue
Normal file
102
components/calendar/PreviewCard.vue
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { PhArchive, PhCalendarDots, PhFile, PhFileDashed, PhFilePlus, PhPencil, PhPencilSimpleLine, PhTrash } from "@phosphor-icons/vue";
|
||||||
|
import { DateTime } from "luxon";
|
||||||
|
import type { Calendar } from "~/models/CalendarConfig";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
calendar: Calendar,
|
||||||
|
gmId?: string,
|
||||||
|
showActions?: boolean,
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits(["on-edit", "on-delete"])
|
||||||
|
|
||||||
|
const { locale } = useI18n();
|
||||||
|
|
||||||
|
const createdAt = DateTime.fromISO(props.calendar.createdAt!).toLocaleString(DateTime.DATETIME_MED, { locale: locale.value });
|
||||||
|
const updatedAt = computed<string>(() => props.calendar.updatedAt ? DateTime.fromISO(props.calendar.updatedAt).toLocaleString(DateTime.DATETIME_MED, { locale: locale.value }) : "");
|
||||||
|
|
||||||
|
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}`);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<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"
|
||||||
|
:link="calendarLink"
|
||||||
|
:class="{
|
||||||
|
'hover:bg-slate-50 dark:hover:bg-sky-950 dark:focus-within:outline-sky-900': !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">
|
||||||
|
<UiCardTitle class="text-xl pr-12">{{ calendar.name }}</UiCardTitle>
|
||||||
|
|
||||||
|
<div v-if="calendar.state === 'published'" class="flex items-center gap-1 text-sm">
|
||||||
|
<PhFile size="20" weight="fill" />
|
||||||
|
<span>{{ $t('ui.contentState.published') }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="calendar.state === 'draft'" class="flex items-center gap-1 text-sm">
|
||||||
|
<PhFileDashed size="20" weight="fill" />
|
||||||
|
<span>{{ $t('ui.contentState.draft') }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="calendar.state === 'archived'" class="flex items-center gap-1 text-sm">
|
||||||
|
<PhArchive size="20" weight="fill" />
|
||||||
|
<span>{{ $t('ui.contentState.archived') }}</span>
|
||||||
|
</div>
|
||||||
|
</UiCardHeader>
|
||||||
|
|
||||||
|
<UiCardContent class="grow">
|
||||||
|
<p class="flex items-center gap-1">
|
||||||
|
<PhCalendarDots size="24" weight="fill" />
|
||||||
|
<span v-if="calendar.eventNb?.[0].count! > 1">{{ $t("entity.calendar.hasXEvents", { count: calendar.eventNb?.[0].count }) }}</span>
|
||||||
|
<span v-else>{{ $t("entity.calendar.hasXEvent", { count: calendar.eventNb?.[0].count }) }}</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="isOwner && showActions"
|
||||||
|
class="flex gap-1 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')">
|
||||||
|
<PhPencil size="16" />
|
||||||
|
</UiButton>
|
||||||
|
|
||||||
|
<UiButton size="icon" variant="ghost" class=" hover:text-white hover:bg-rose-400 dark:hover:bg-rose-700" @click="emit('on-delete')">
|
||||||
|
<PhTrash size="16" />
|
||||||
|
</UiButton>
|
||||||
|
</div>
|
||||||
|
</UiCardContent>
|
||||||
|
|
||||||
|
<UiCardFooter>
|
||||||
|
<ul class="grid gap-1 text-sm">
|
||||||
|
<li class="flex gap-1 items-center">
|
||||||
|
<PhFilePlus size="18" />
|
||||||
|
<span>{{ $t('common.createdAt', { createdAt }) }}</span>
|
||||||
|
</li>
|
||||||
|
<li v-if="updatedAt" class="flex gap-1 items-center">
|
||||||
|
<PhPencilSimpleLine size="18" />
|
||||||
|
<span>{{ $t('common.updatedAt', { updatedAt }) }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</UiCardFooter>
|
||||||
|
</UiCard>
|
||||||
|
</template>
|
||||||
15
components/calendar/category/List.vue
Normal file
15
components/calendar/category/List.vue
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { Category } from "~/models/Category";
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
categories: Category[]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ul>
|
||||||
|
<li v-for="category in categories" :key="category.id">
|
||||||
|
<CalendarCategoryListItem :category="category" />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
15
components/calendar/category/ListItem.vue
Normal file
15
components/calendar/category/ListItem.vue
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { Category } from "~/models/Category";
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
category: Category
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-sm text-gray-700 dark:text-gray-300">
|
||||||
|
{{ category.name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
72
components/calendar/category/Table.vue
Normal file
72
components/calendar/category/Table.vue
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { Category } from "~/models/Category";
|
||||||
|
import { ScrollAreaRoot, ScrollAreaViewport, ScrollAreaScrollbar, ScrollAreaThumb } from "radix-vue"
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const { categories } = defineProps<{
|
||||||
|
categories: Category[]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const sortedCategories = computed(() => categories.toSorted((a, b) => a.name.localeCompare(b.name)))
|
||||||
|
|
||||||
|
const deleteDialogOpened = ref<boolean>(false)
|
||||||
|
|
||||||
|
function openDeleteDialog() {
|
||||||
|
deleteDialogOpened.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDeleteDialog() {
|
||||||
|
deleteDialogOpened.value = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col h-full">
|
||||||
|
<CalendarCategoryTableHeader />
|
||||||
|
|
||||||
|
<ScrollAreaRoot class="h-36 grow overflow-hidden ">
|
||||||
|
<ScrollAreaViewport class="w-full h-full pr-4" as-child>
|
||||||
|
<div class="last:border-0">
|
||||||
|
<CalendarCategoryTableRow
|
||||||
|
v-for="item in sortedCategories"
|
||||||
|
:key="item.id"
|
||||||
|
:category="item"
|
||||||
|
@on-delete-category="openDeleteDialog"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ScrollAreaViewport>
|
||||||
|
<ScrollAreaScrollbar
|
||||||
|
class="flex select-none touch-none p-0.5 bg-background transition-colors duration-100 ease-out hover:bg-foreground/5 data-[orientation=vertical]:w-2.5 data-[orientation=horizontal]:flex-col data-[orientation=horizontal]:h-2.5"
|
||||||
|
orientation="vertical"
|
||||||
|
>
|
||||||
|
<ScrollAreaThumb
|
||||||
|
class="flex-1 bg-foreground/20 rounded-[10px] relative before:content-[''] before:absolute before:top-1/2 before:left-1/2 before:-translate-x-1/2 before:-translate-y-1/2 before:w-full before:h-full"
|
||||||
|
/>
|
||||||
|
</ScrollAreaScrollbar>
|
||||||
|
</ScrollAreaRoot>
|
||||||
|
|
||||||
|
<CalendarCategoryTableFooter />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<UiDialog v-model:open="deleteDialogOpened">
|
||||||
|
<UiDialogContent
|
||||||
|
:disable-outside-pointer-events="true"
|
||||||
|
:trap-focus="true"
|
||||||
|
class="min-w-96 border-indigo-200 dark:bg-slate-950 dark:border-indigo-950"
|
||||||
|
@escape-key-down="closeDeleteDialog"
|
||||||
|
@focus-outside="closeDeleteDialog"
|
||||||
|
@interact-outside="closeDeleteDialog"
|
||||||
|
@pointer-down-outside="closeDeleteDialog"
|
||||||
|
>
|
||||||
|
<UiDialogTitle>
|
||||||
|
{{ $t('entity.category.deleteDialog.title') }}
|
||||||
|
</UiDialogTitle>
|
||||||
|
<UiDialogDescription>
|
||||||
|
{{ $t('entity.category.deleteDialog.subtitle') }}
|
||||||
|
</UiDialogDescription>
|
||||||
|
|
||||||
|
<CalendarFormDeleteCategory />
|
||||||
|
</UiDialogContent>
|
||||||
|
</UiDialog>
|
||||||
|
</template>
|
||||||
130
components/calendar/category/TableFooter.vue
Normal file
130
components/calendar/category/TableFooter.vue
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { PhPlus } from "@phosphor-icons/vue"
|
||||||
|
import { useToast } from "~/components/ui/toast"
|
||||||
|
import { ToastLifetime } from "~/components/ui/toast/use-toast"
|
||||||
|
import { cn } from "~/lib/utils"
|
||||||
|
|
||||||
|
const { toast } = useToast()
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
type FooterMode = "add" | "view"
|
||||||
|
const currentMode = ref<FooterMode>("view")
|
||||||
|
|
||||||
|
const rowRef = ref<HTMLDivElement | null>(null)
|
||||||
|
const inputRef = ref<HTMLInputElement | null>(null)
|
||||||
|
const { focused: inputFocused } = useFocus(inputRef)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle view mode options
|
||||||
|
*/
|
||||||
|
type ToggleViewOptions = {
|
||||||
|
execution?: "now" | "nextTick"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle view mode
|
||||||
|
* @param options.execution - When to execute the toggle. "now" or "nextTick"
|
||||||
|
*/
|
||||||
|
function toggleView(options: ToggleViewOptions = { execution: "now" }) {
|
||||||
|
currentMode.value = "view"
|
||||||
|
|
||||||
|
if (options.execution === "now") {
|
||||||
|
inputFocused.value = false
|
||||||
|
} else {
|
||||||
|
nextTick(() => {
|
||||||
|
inputFocused.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle add mode
|
||||||
|
*/
|
||||||
|
function toggleAdd() {
|
||||||
|
currentMode.value = "add"
|
||||||
|
categorySkeleton.value = { name: "", color: "black" }
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
inputFocused.value = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickOutside(rowRef, () => toggleView({ execution: "nextTick" }))
|
||||||
|
onKeyStroke("Escape", () => toggleView({ execution: "now" }))
|
||||||
|
|
||||||
|
const { addCategoryFromSkeleton } = useCategoryStore()
|
||||||
|
const { categorySkeleton } = storeToRefs(useCategoryStore())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit the update
|
||||||
|
*/
|
||||||
|
async function submitNew() {
|
||||||
|
if (!categorySkeleton.value) return
|
||||||
|
const newCategoryName = toRaw(categorySkeleton.value).name
|
||||||
|
|
||||||
|
const { error } = await tryCatch(addCategoryFromSkeleton())
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
toast({
|
||||||
|
title: t("entity.category.addedToast.titleError", { category: newCategoryName }),
|
||||||
|
variant: "destructive",
|
||||||
|
duration: ToastLifetime.LONG
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleView({ execution: "now" })
|
||||||
|
toast({
|
||||||
|
title: t("entity.category.addedToast.title", { category: newCategoryName }),
|
||||||
|
variant: "success",
|
||||||
|
duration: ToastLifetime.SHORT
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="border-y-[1px] border-b-foreground/10 mr-4">
|
||||||
|
<form
|
||||||
|
ref="rowRef"
|
||||||
|
class="grid grid-cols-12 items-center gap-4 p-1 bg-transparent hover:bg-slate-50 dark:bg-transparent dark:hover:bg-slate-900"
|
||||||
|
@submit.prevent="submitNew"
|
||||||
|
>
|
||||||
|
<div v-if="currentMode === 'add'" class="col-span-1" />
|
||||||
|
<div
|
||||||
|
:class="cn({
|
||||||
|
'col-span-6': currentMode === 'view',
|
||||||
|
'col-span-5': currentMode === 'add'
|
||||||
|
})"
|
||||||
|
>
|
||||||
|
<template v-if="currentMode === 'view'">
|
||||||
|
<button
|
||||||
|
class="p-2 h-full w-full text-left underline-offset-4 hover:underline cursor-pointer"
|
||||||
|
@click="toggleAdd"
|
||||||
|
>
|
||||||
|
<PhPlus size="18" />
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<template v-if="currentMode === 'add' && categorySkeleton">
|
||||||
|
<input
|
||||||
|
ref="inputRef"
|
||||||
|
v-model="categorySkeleton.name"
|
||||||
|
type="text"
|
||||||
|
class="p-1 h-full w-full bg-transparent focus-visible:outline-hidden italic"
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="col-span-4">
|
||||||
|
<template v-if="currentMode === 'add' && categorySkeleton">
|
||||||
|
<div class="-mx-2">
|
||||||
|
<InputColor
|
||||||
|
id="category-color"
|
||||||
|
v-model="categorySkeleton.color"
|
||||||
|
position="item-aligned"
|
||||||
|
theme="subtle"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
19
components/calendar/category/TableHeader.vue
Normal file
19
components/calendar/category/TableHeader.vue
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { PhListBullets } from "@phosphor-icons/vue";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<header class="grid grid-cols-12 gap-4 px-2 py-4 border-b-[1px] border-b-border font-bold mr-4">
|
||||||
|
<div class="col-span-1 flex items-end">
|
||||||
|
<PhListBullets size="20" />
|
||||||
|
</div>
|
||||||
|
<div class="col-span-5">
|
||||||
|
Nom
|
||||||
|
</div>
|
||||||
|
<div class="col-span-4">
|
||||||
|
Couleur
|
||||||
|
</div>
|
||||||
|
<div class="col-span-2" />
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
|
||||||
187
components/calendar/category/TableRow.vue
Normal file
187
components/calendar/category/TableRow.vue
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { PhCheck, PhTrash } from "@phosphor-icons/vue"
|
||||||
|
import { useToast } from "~/components/ui/toast"
|
||||||
|
import { ToastLifetime } from "~/components/ui/toast/use-toast"
|
||||||
|
import { cn } from "~/lib/utils"
|
||||||
|
import type { Category } from "~/models/Category"
|
||||||
|
|
||||||
|
const { toast } = useToast()
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const { category } = defineProps<{
|
||||||
|
category: Category
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "on-delete-category", payload: Category): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
type RowMode = "edit" | "view"
|
||||||
|
const currentMode = ref<RowMode>("view")
|
||||||
|
|
||||||
|
const rowRef = ref<HTMLDivElement | null>(null)
|
||||||
|
const inputRef = ref<HTMLInputElement | null>(null)
|
||||||
|
const { focused: inputFocused } = useFocus(inputRef)
|
||||||
|
const rowHovered = useElementHover(rowRef)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle view mode options
|
||||||
|
*/
|
||||||
|
type ToggleViewOptions = {
|
||||||
|
execution?: "now" | "nextTick"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle view mode
|
||||||
|
* @param options.execution - When to execute the toggle. "now" or "nextTick"
|
||||||
|
*/
|
||||||
|
function toggleView(options: ToggleViewOptions = { execution: "now" }) {
|
||||||
|
currentMode.value = "view"
|
||||||
|
|
||||||
|
if (options.execution === "now") {
|
||||||
|
inputFocused.value = false
|
||||||
|
} else {
|
||||||
|
nextTick(() => {
|
||||||
|
inputFocused.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle edit mode
|
||||||
|
*/
|
||||||
|
function toggleEdit() {
|
||||||
|
currentMode.value = "edit"
|
||||||
|
categorySkeleton.value = structuredClone(toRaw(category))
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
inputFocused.value = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickOutside(rowRef, () => toggleView({ execution: "nextTick" }))
|
||||||
|
onKeyStroke("Escape", () => toggleView({ execution: "now" }))
|
||||||
|
|
||||||
|
const { resetSkeleton, updateCategoryFromSkeleton } = useCategoryStore()
|
||||||
|
const { categorySkeleton } = storeToRefs(useCategoryStore())
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
resetSkeleton()
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit the update
|
||||||
|
*/
|
||||||
|
async function submitUpdate() {
|
||||||
|
const { error } = await tryCatch(updateCategoryFromSkeleton())
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
toast({
|
||||||
|
title: t("entity.category.updatedToast.titleError", { category: category.name }),
|
||||||
|
variant: "destructive",
|
||||||
|
duration: ToastLifetime.LONG
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleView({ execution: "now" })
|
||||||
|
toast({
|
||||||
|
title: t("entity.category.updatedToast.title", { category: category.name }),
|
||||||
|
variant: "success",
|
||||||
|
duration: ToastLifetime.SHORT
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleQueryDelete() {
|
||||||
|
categorySkeleton.value = structuredClone(toRaw(category))
|
||||||
|
emit("on-delete-category", category)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="rowRef" class="relative">
|
||||||
|
<form
|
||||||
|
class="grid grid-cols-12 items-center gap-4 p-1 border-b-[1px] border-b-foreground/10"
|
||||||
|
:class="cn(
|
||||||
|
{ 'bg-slate-100 hover:bg-slate-200 dark:bg-slate-900 dark:hover:bg-slate-800': currentMode === 'edit' },
|
||||||
|
{ 'bg-transparent hover:bg-slate-50 dark:bg-transparent dark:hover:bg-slate-900': currentMode !== 'edit' }
|
||||||
|
)"
|
||||||
|
@submit.prevent="submitUpdate"
|
||||||
|
>
|
||||||
|
<div class="col-span-1 pointer-events-none">
|
||||||
|
<span class="opacity-50 p-2">{{ category.id }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="col-span-5"
|
||||||
|
>
|
||||||
|
<template v-if="currentMode === 'view'">
|
||||||
|
<button
|
||||||
|
class="py-2 px-1 h-full w-full text-left underline-offset-4 hover:underline cursor-pointer"
|
||||||
|
@click="toggleEdit"
|
||||||
|
>
|
||||||
|
{{ category.name }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="currentMode === 'edit' && categorySkeleton">
|
||||||
|
<input
|
||||||
|
ref="inputRef"
|
||||||
|
v-model="categorySkeleton.name"
|
||||||
|
type="text"
|
||||||
|
class="p-1 h-full w-full bg-transparent focus-visible:outline-hidden italic"
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="col-span-4">
|
||||||
|
<template v-if="currentMode === 'view'">
|
||||||
|
<button
|
||||||
|
class="p-1 h-full w-full text-left text-sm cursor-pointer"
|
||||||
|
@click="toggleEdit"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="bgc"
|
||||||
|
:class="cn(`element-${category.color}`)"
|
||||||
|
>
|
||||||
|
{{ $t(`ui.colors.${category.color}`) }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="currentMode === 'edit' && categorySkeleton">
|
||||||
|
<div class="-mx-2">
|
||||||
|
<InputColor
|
||||||
|
id="category-color"
|
||||||
|
v-model="categorySkeleton.color"
|
||||||
|
position="item-aligned"
|
||||||
|
theme="subtle"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<menu class="w-fit absolute top-1/2 -translate-y-1/2 right-2 flex items-center gap-2">
|
||||||
|
<li
|
||||||
|
v-if="currentMode === 'edit' && categorySkeleton"
|
||||||
|
>
|
||||||
|
<UiButton
|
||||||
|
variant="secondary"
|
||||||
|
size="icon"
|
||||||
|
class="w-6 h-6 rounded-full bg-emerald-500 hover:bg-emerald-600 text-white"
|
||||||
|
:title="$t('ui.actions.edit')"
|
||||||
|
@click="submitUpdate"
|
||||||
|
>
|
||||||
|
<PhCheck size="14" weight="bold" />
|
||||||
|
</UiButton>
|
||||||
|
</li>
|
||||||
|
<li v-else-if="rowHovered">
|
||||||
|
<UiButton
|
||||||
|
variant="secondary"
|
||||||
|
size="icon"
|
||||||
|
class="w-6 h-6 rounded-full hover:bg-red-600 hover:text-white"
|
||||||
|
:title="$t('ui.actions.delete')"
|
||||||
|
@click="handleQueryDelete"
|
||||||
|
>
|
||||||
|
<PhTrash size="14" weight="bold" />
|
||||||
|
</UiButton>
|
||||||
|
</li>
|
||||||
|
</menu>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
41
components/calendar/dialog/Categories.vue
Normal file
41
components/calendar/dialog/Categories.vue
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { PhX } from "@phosphor-icons/vue";
|
||||||
|
|
||||||
|
const { toggleCategoriesModal } = useCalendar()
|
||||||
|
const { categories, isCategoriesModalOpen } = storeToRefs(useCalendar())
|
||||||
|
|
||||||
|
function handleClosing() {
|
||||||
|
toggleCategoriesModal(false)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UiAlertDialog :open="isCategoriesModalOpen">
|
||||||
|
<UiAlertDialogContent
|
||||||
|
class="grid grid-rows-[auto_1fr_auto] items-start min-h-[66vh] max-w-4xl"
|
||||||
|
:disable-outside-pointer-events="true"
|
||||||
|
:trap-focus="true"
|
||||||
|
@focus-outside="handleClosing"
|
||||||
|
@interact-outside="handleClosing"
|
||||||
|
@close-auto-focus="(e) => e.preventDefault()"
|
||||||
|
>
|
||||||
|
<header>
|
||||||
|
<UiAlertDialogTitle>
|
||||||
|
<span class="text-2xl">
|
||||||
|
{{ $t('entity.category.manageDialog.title') }}
|
||||||
|
</span>
|
||||||
|
</UiAlertDialogTitle>
|
||||||
|
|
||||||
|
<UiAlertDialogDescription>
|
||||||
|
{{ $t('entity.category.manageDialog.subtitle') }}
|
||||||
|
</UiAlertDialogDescription>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<UiButton size="icon" variant="ghost" class="absolute top-4 right-4" title="Fermer la fenêtre" @click="handleClosing">
|
||||||
|
<PhX size="20" />
|
||||||
|
</UiButton>
|
||||||
|
|
||||||
|
<CalendarCategoryTable :categories />
|
||||||
|
</UiAlertDialogContent>
|
||||||
|
</UiAlertDialog>
|
||||||
|
</template>
|
||||||
@@ -17,12 +17,13 @@ const emit = defineEmits(["on-close"])
|
|||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
emit("on-close")
|
emit("on-close")
|
||||||
|
setTimeout(() => calendarSkeletonName.value = "", 100)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<UiAlertDialog :open="modalState">
|
<UiAlertDialog :open="modalState">
|
||||||
<UiAlertDialogContent class="grid grid-rows-[auto_1fr_auto] items-start min-h-[66vh] max-w-4xl gap-6">
|
<UiAlertDialogContent class="grid grid-rows-[auto_1fr_auto] items-start min-h-[66vh] max-w-4xl gap-6" @close-auto-focus="(e) => e.preventDefault()">
|
||||||
<UiAlertDialogTitle>
|
<UiAlertDialogTitle>
|
||||||
<span class="text-2xl">
|
<span class="text-2xl">
|
||||||
<strong class="font-bold">{{ world.name }}</strong>
|
<strong class="font-bold">{{ world.name }}</strong>
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ function openEventCreatePopover() {
|
|||||||
*
|
*
|
||||||
* @param e The closing event (can be keydown or click)
|
* @param e The closing event (can be keydown or click)
|
||||||
*/
|
*/
|
||||||
function handleClosing(e: Event) {
|
function handleClosing() {
|
||||||
e.preventDefault()
|
|
||||||
popoverOpen.value = false
|
popoverOpen.value = false
|
||||||
|
resetSkeleton()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -53,8 +53,8 @@ function handleClosing(e: Event) {
|
|||||||
:disable-outside-pointer-events="true"
|
:disable-outside-pointer-events="true"
|
||||||
:trap-focus="true"
|
:trap-focus="true"
|
||||||
class="pl-3 w-[30rem] max-w-full border-indigo-200 dark:bg-slate-950 dark:border-indigo-950"
|
class="pl-3 w-[30rem] max-w-full border-indigo-200 dark:bg-slate-950 dark:border-indigo-950"
|
||||||
@escape-key-down="handleClosing"
|
@escape-key-down.prevent="handleClosing"
|
||||||
@pointer-down-outside="handleClosing"
|
@pointer-down-outside.prevent="handleClosing"
|
||||||
>
|
>
|
||||||
<CalendarFormCreateEvent @event-created="handleClosing" />
|
<CalendarFormCreateEvent @event-created="handleClosing" />
|
||||||
</UiPopoverContent>
|
</UiPopoverContent>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { PhCircleNotch } from "@phosphor-icons/vue";
|
import { PhCircleNotch } from "@phosphor-icons/vue";
|
||||||
import { useToast } from "~/components/ui/toast";
|
import { useToast } from "~/components/ui/toast";
|
||||||
|
import { ToastLifetime } from "~/components/ui/toast/use-toast";
|
||||||
import type { Calendar } from "~/models/CalendarConfig";
|
import type { Calendar } from "~/models/CalendarConfig";
|
||||||
|
|
||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
@@ -28,7 +29,7 @@ async function handleAction(): Promise<void> {
|
|||||||
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: 3000
|
duration: ToastLifetime.SHORT
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
@@ -65,6 +66,7 @@ function handleClosing() {
|
|||||||
@focus-outside="handleClosing"
|
@focus-outside="handleClosing"
|
||||||
@interact-outside="handleClosing"
|
@interact-outside="handleClosing"
|
||||||
@pointer-down-outside="handleClosing"
|
@pointer-down-outside="handleClosing"
|
||||||
|
@close-auto-focus="(e) => e.preventDefault()"
|
||||||
>
|
>
|
||||||
<UiAlertDialogTitle>
|
<UiAlertDialogTitle>
|
||||||
{{ $t('entity.calendar.deleteDialog.title') }}
|
{{ $t('entity.calendar.deleteDialog.title') }}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const isLoading = ref<boolean>(false)
|
|||||||
// Watch the popover state
|
// Watch the popover state
|
||||||
watch(isDeleteEventModalOpen, (hasOpened, _o) => {
|
watch(isDeleteEventModalOpen, (hasOpened, _o) => {
|
||||||
if (hasOpened && lastActiveEvent.value) {
|
if (hasOpened && lastActiveEvent.value) {
|
||||||
eventSkeleton.value = { ...lastActiveEvent.value }
|
eventSkeleton.value = structuredClone(toRaw(lastActiveEvent.value))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,19 @@
|
|||||||
import { PhPlus } from "@phosphor-icons/vue";
|
import { PhPlus } from "@phosphor-icons/vue";
|
||||||
|
|
||||||
const isDialogOpen = ref<boolean>(false);
|
const isDialogOpen = ref<boolean>(false);
|
||||||
|
const { resetSkeleton } = useCalendar();
|
||||||
|
|
||||||
// Toggles the dialog
|
// Toggles the dialog
|
||||||
function toggleDialog() {
|
function toggleDialog() {
|
||||||
isDialogOpen.value = !isDialogOpen.value;
|
isDialogOpen.value = !isDialogOpen.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevents the modal from closing if's still loading
|
||||||
|
*/
|
||||||
|
function handleClosing() {
|
||||||
|
setTimeout(() => resetSkeleton(), 100)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -19,7 +27,11 @@ function toggleDialog() {
|
|||||||
</UiButton>
|
</UiButton>
|
||||||
|
|
||||||
<UiDialog v-model:open="isDialogOpen">
|
<UiDialog v-model:open="isDialogOpen">
|
||||||
<UiDialogContent class="border-indigo-200 dark:bg-slate-950 dark:border-indigo-950">
|
<UiDialogContent
|
||||||
|
class="border-indigo-200 dark:bg-slate-950 dark:border-indigo-950"
|
||||||
|
@escape-key-down.prevent="handleClosing"
|
||||||
|
@pointer-down-outside.prevent="handleClosing"
|
||||||
|
>
|
||||||
<UiDialogTitle>
|
<UiDialogTitle>
|
||||||
{{ $t("entity.calendar.event.addSingle") }}
|
{{ $t("entity.calendar.event.addSingle") }}
|
||||||
</UiDialogTitle>
|
</UiDialogTitle>
|
||||||
|
|||||||
49
components/calendar/dialog/Update.vue
Normal file
49
components/calendar/dialog/Update.vue
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { PhX } from "@phosphor-icons/vue";
|
||||||
|
import type { Calendar } from "~/models/CalendarConfig";
|
||||||
|
import type { World } from "~/models/World";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
calendar: Calendar | null,
|
||||||
|
world: World | null,
|
||||||
|
modalState?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const calendarSkeletonName = ref<string>(props.calendar?.name ?? "")
|
||||||
|
|
||||||
|
function onChangedName(newName: string) {
|
||||||
|
calendarSkeletonName.value = newName
|
||||||
|
}
|
||||||
|
|
||||||
|
const emit = defineEmits(["on-close"])
|
||||||
|
|
||||||
|
function handleClose() {
|
||||||
|
emit("on-close")
|
||||||
|
setTimeout(() => calendarSkeletonName.value = "", 100)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UiAlertDialog :open="modalState">
|
||||||
|
<UiAlertDialogContent class="grid grid-rows-[auto_1fr_auto] items-start min-h-[66vh] max-w-4xl gap-6" @close-auto-focus="(e) => e.preventDefault()">
|
||||||
|
<UiAlertDialogTitle>
|
||||||
|
<span class="text-2xl">
|
||||||
|
<strong class="font-bold">{{ world?.name }}</strong>
|
||||||
|
<span class="opacity-30"> — </span>
|
||||||
|
<span v-if="calendarSkeletonName">
|
||||||
|
{{ calendarSkeletonName }}
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
{{ $t('entity.calendar.updateDialog.title') }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</UiAlertDialogTitle>
|
||||||
|
|
||||||
|
<UiButton size="icon" variant="ghost" class="absolute top-4 right-4" title="Fermer la fenêtre" @click="handleClose">
|
||||||
|
<PhX size="20" />
|
||||||
|
</UiButton>
|
||||||
|
|
||||||
|
<CalendarFormUpdate v-if="calendar" :world="world" :calendar="calendar" @on-changed-name="onChangedName" @on-close="handleClose" />
|
||||||
|
</UiAlertDialogContent>
|
||||||
|
</UiAlertDialog>
|
||||||
|
</template>
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
const { eventSkeleton, lastActiveEvent, isEditEventModalOpen } = storeToRefs(useCalendar())
|
const { eventSkeleton, lastActiveEvent, isEditEventModalOpen } = storeToRefs(useCalendar())
|
||||||
|
const { resetSkeleton } = useCalendar();
|
||||||
|
|
||||||
// Watch the popover state
|
// Watch the popover state
|
||||||
watch(isEditEventModalOpen, (hasOpened, _o) => {
|
watch(isEditEventModalOpen, (hasOpened, _o) => {
|
||||||
if (hasOpened && lastActiveEvent.value) {
|
if (hasOpened && lastActiveEvent.value) {
|
||||||
eventSkeleton.value = { ...lastActiveEvent.value }
|
eventSkeleton.value = structuredClone(toRaw(lastActiveEvent.value))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ watch(isEditEventModalOpen, (hasOpened, _o) => {
|
|||||||
*/
|
*/
|
||||||
function handleClosing() {
|
function handleClosing() {
|
||||||
isEditEventModalOpen.value = false
|
isEditEventModalOpen.value = false
|
||||||
|
setTimeout(() => resetSkeleton(), 100)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -23,7 +25,7 @@ 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 min-w-96 border-border"
|
||||||
@escape-key-down="handleClosing"
|
@escape-key-down="handleClosing"
|
||||||
@focus-outside="handleClosing"
|
@focus-outside="handleClosing"
|
||||||
@interact-outside="handleClosing"
|
@interact-outside="handleClosing"
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ function handleJumpToDate(date: RPGDate) {
|
|||||||
const commandMenuOpened = ref(false)
|
const commandMenuOpened = ref(false)
|
||||||
|
|
||||||
function deployEditModal() {
|
function deployEditModal() {
|
||||||
lastActiveEvent.value = { ...props.event }
|
lastActiveEvent.value = structuredClone(toRaw(props.event))
|
||||||
revealEditEventModal()
|
revealEditEventModal()
|
||||||
commandMenuOpened.value = false
|
commandMenuOpened.value = false
|
||||||
emit("query:close-popover")
|
emit("query:close-popover")
|
||||||
@@ -53,7 +53,7 @@ function deployEditModal() {
|
|||||||
* Confirm event deletion
|
* Confirm event deletion
|
||||||
*/
|
*/
|
||||||
function deployDeleteModal() {
|
function deployDeleteModal() {
|
||||||
lastActiveEvent.value = { ...props.event }
|
lastActiveEvent.value = structuredClone(toRaw(props.event))
|
||||||
revealDeleteEventModal()
|
revealDeleteEventModal()
|
||||||
commandMenuOpened.value = false
|
commandMenuOpened.value = false
|
||||||
emit("query:close-popover")
|
emit("query:close-popover")
|
||||||
@@ -62,17 +62,14 @@ function deployDeleteModal() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<UiPopoverContent
|
<UiPopoverContent
|
||||||
class="w-96"
|
class="w-96 event-popover"
|
||||||
:align="'center'"
|
:align="'center'"
|
||||||
:align-offset="50"
|
:align-offset="50"
|
||||||
:side="'left'"
|
:side="'left'"
|
||||||
:collision-padding="60"
|
:collision-padding="60"
|
||||||
:hide-when-detached="true"
|
:hide-when-detached="true"
|
||||||
: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 +140,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 }}
|
||||||
@@ -23,13 +23,13 @@ const isPopoverDetailsOpen = ref<boolean>(false)
|
|||||||
|
|
||||||
function handleDoubleClick() {
|
function handleDoubleClick() {
|
||||||
isPopoverDetailsOpen.value = false
|
isPopoverDetailsOpen.value = false
|
||||||
lastActiveEvent.value = { ...props.event }
|
lastActiveEvent.value = structuredClone(toRaw(props.event))
|
||||||
revealEditEventModal()
|
revealEditEventModal()
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
isPopoverDetailsOpen.value = false
|
isPopoverDetailsOpen.value = false
|
||||||
lastActiveEvent.value = { ...props.event }
|
lastActiveEvent.value = structuredClone(toRaw(props.event))
|
||||||
revealDeleteEventModal()
|
revealDeleteEventModal()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,12 +55,12 @@ onMounted(() => {
|
|||||||
<UiPopover v-model:open="isPopoverDetailsOpen">
|
<UiPopover v-model:open="isPopoverDetailsOpen">
|
||||||
<UiPopoverTrigger as-child>
|
<UiPopoverTrigger as-child>
|
||||||
<button
|
<button
|
||||||
class="text-xs px-2 py-1 block w-full text-left rounded-sm focus-visible:bg-red-200"
|
class="event-button text-xs px-2 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}`,
|
||||||
{
|
{
|
||||||
'italic': event.hidden,
|
'is-hidden': event.hidden,
|
||||||
'rounded-r-none': isStartEvent,
|
'rounded-r-none': isStartEvent,
|
||||||
'rounded-l-none': isEndEvent,
|
'rounded-l-none': isEndEvent,
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
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" }
|
const defaultSkeleton: Calendar = { name: "", today: { day: 1, month: 0, year: 0 }, months: [], events: [], state: "draft", color: "white" }
|
||||||
const calendarSkeleton = ref<Calendar>({ ...defaultSkeleton })
|
const calendarSkeleton = ref<Calendar>({ ...defaultSkeleton })
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -79,25 +79,25 @@ function handleFormCancel() {
|
|||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<PhWrench size="18" weight="fill" />
|
<PhWrench size="18" weight="fill" />
|
||||||
|
|
||||||
{{ $t('entity.calendar.createDialog.tabs.general.title') }}
|
{{ $t('entity.calendar.actionDialog.tabs.general.title') }}
|
||||||
</div>
|
</div>
|
||||||
</UiTabsTrigger>
|
</UiTabsTrigger>
|
||||||
<UiTabsTrigger value="months" class="font-bold">
|
<UiTabsTrigger value="months" class="font-bold">
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<PhCalendarDots size="18" weight="fill" />
|
<PhCalendarDots size="18" weight="fill" />
|
||||||
|
|
||||||
{{ $t('entity.calendar.createDialog.tabs.months.title') }}
|
{{ $t('entity.calendar.actionDialog.tabs.months.title') }}
|
||||||
</div>
|
</div>
|
||||||
</UiTabsTrigger>
|
</UiTabsTrigger>
|
||||||
<UiTabsTrigger value="today" class="font-bold">
|
<UiTabsTrigger value="today" class="font-bold">
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<PhAlarm size="18" weight="fill" />
|
<PhAlarm size="18" weight="fill" />
|
||||||
|
|
||||||
{{ $t('entity.calendar.createDialog.tabs.today.title') }}
|
{{ $t('entity.calendar.actionDialog.tabs.today.title') }}
|
||||||
</div>
|
</div>
|
||||||
</UiTabsTrigger>
|
</UiTabsTrigger>
|
||||||
</UiTabsList>
|
</UiTabsList>
|
||||||
<UiTabsContent value="global">
|
<UiTabsContent value="global" class="grid gap-4">
|
||||||
<input
|
<input
|
||||||
id="new-calendar-name"
|
id="new-calendar-name"
|
||||||
v-model="calendarSkeleton.name"
|
v-model="calendarSkeleton.name"
|
||||||
@@ -105,9 +105,25 @@ function handleFormCancel() {
|
|||||||
name="new-calendar-name"
|
name="new-calendar-name"
|
||||||
required
|
required
|
||||||
:placeholder="$t('common.title')"
|
:placeholder="$t('common.title')"
|
||||||
class="w-full -my-1 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"
|
||||||
>
|
>
|
||||||
|
|
||||||
|
<div class="-mx-1 px-1 grid gap-3">
|
||||||
|
<UiLabel for="new-calendar-state">
|
||||||
|
{{ $t('ui.contentState.label') }}
|
||||||
|
</UiLabel>
|
||||||
|
|
||||||
|
<InputContentState id="new-calendar-state" v-model="calendarSkeleton.state" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="-mx-1 grid gap-3">
|
||||||
|
<UiLabel for="new-calendar-color">
|
||||||
|
{{ $t('ui.colors.label') }}
|
||||||
|
</UiLabel>
|
||||||
|
|
||||||
|
<InputColor id="new-calendar-color" v-model="calendarSkeleton.color" />
|
||||||
|
</div>
|
||||||
</UiTabsContent>
|
</UiTabsContent>
|
||||||
<UiTabsContent value="months">
|
<UiTabsContent value="months">
|
||||||
<CalendarInputMonthList v-model:model-value="calendarSkeleton.months" />
|
<CalendarInputMonthList v-model:model-value="calendarSkeleton.months" />
|
||||||
|
|||||||
@@ -2,12 +2,20 @@
|
|||||||
import type { RPGDate } from "~/models/Date";
|
import type { RPGDate } from "~/models/Date";
|
||||||
import { PhAlarm, PhCircleNotch, PhEye, PhEyeClosed, PhMapPinArea, PhTag } from "@phosphor-icons/vue"
|
import { PhAlarm, PhCircleNotch, PhEye, PhEyeClosed, PhMapPinArea, PhTag } from "@phosphor-icons/vue"
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
const emit = defineEmits(["event-created"])
|
const emit = defineEmits(["event-created"])
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
date?: RPGDate
|
||||||
|
btnClass?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
const { eventSkeleton } = storeToRefs(useCalendar())
|
const { eventSkeleton } = storeToRefs(useCalendar())
|
||||||
const { submitSkeleton, cancelLatestRequest } = useCalendar()
|
const { submitSkeleton, cancelLatestRequest } = useCalendar()
|
||||||
|
|
||||||
const { t } = useI18n()
|
const newEventTitle = shallowRef<HTMLInputElement>()
|
||||||
|
useFocus(newEventTitle, { initialValue: true })
|
||||||
|
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
|
|
||||||
@@ -15,11 +23,6 @@ const formErrors = reactive<{ message: string | null }>({
|
|||||||
message: null
|
message: null
|
||||||
})
|
})
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
date?: RPGDate
|
|
||||||
btnClass?: string
|
|
||||||
}>()
|
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
// Prevent form submission if already loading
|
// Prevent form submission if already loading
|
||||||
if (isLoading.value) return
|
if (isLoading.value) return
|
||||||
@@ -56,6 +59,7 @@ function handleCancel() {
|
|||||||
<div class="col-span-2 pl-8">
|
<div class="col-span-2 pl-8">
|
||||||
<input
|
<input
|
||||||
id="new-event-title"
|
id="new-event-title"
|
||||||
|
ref="newEventTitle"
|
||||||
v-model="eventSkeleton.title"
|
v-model="eventSkeleton.title"
|
||||||
type="text"
|
type="text"
|
||||||
name="new-event-title"
|
name="new-event-title"
|
||||||
@@ -63,7 +67,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') }}
|
||||||
@@ -77,7 +81,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') }}
|
||||||
@@ -137,7 +141,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') }}
|
||||||
|
|||||||
85
components/calendar/form/DeleteCategory.vue
Normal file
85
components/calendar/form/DeleteCategory.vue
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { PhCircleNotch } from "@phosphor-icons/vue";
|
||||||
|
import { useToast } from "~/components/ui/toast";
|
||||||
|
import { ToastLifetime } from "~/components/ui/toast/use-toast";
|
||||||
|
|
||||||
|
const { deleteCategoryFromSkeleton, cancelLatestRequest } = useCategoryStore()
|
||||||
|
const { isDeletingCategory, categorySkeleton } = storeToRefs(useCategoryStore())
|
||||||
|
|
||||||
|
const { toast } = useToast()
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const isLoading = ref<boolean>(false)
|
||||||
|
|
||||||
|
const formErrors = reactive<{ message: string | null }>({
|
||||||
|
message: null
|
||||||
|
})
|
||||||
|
|
||||||
|
async function handleAction(): Promise<void> {
|
||||||
|
if (isLoading.value && !categorySkeleton.value) return
|
||||||
|
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
const categoryName = categorySkeleton.value?.name
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deleteCategoryFromSkeleton()
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: t("entity.category.deletedToast.title", { category: categoryName }),
|
||||||
|
variant: "success",
|
||||||
|
duration: ToastLifetime.MEDIUM
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof Error) {
|
||||||
|
formErrors.message = err.message
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Click on the cancel button
|
||||||
|
*
|
||||||
|
* Must cancel the abortController in the store, and stop the loading
|
||||||
|
*/
|
||||||
|
function handleCancel(): void {
|
||||||
|
cancelLatestRequest()
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<form @submit.prevent="handleAction">
|
||||||
|
<div class="grid grid-cols-2 gap-y-4">
|
||||||
|
<div class="text-red-500 ml-8">
|
||||||
|
<span class="text-sm">
|
||||||
|
{{ formErrors.message }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="flex gap-2 justify-between">
|
||||||
|
<UiButton type="button" size="sm" variant="outline" @click="() => isDeletingCategory = false">
|
||||||
|
{{ $t('ui.action.back') }}
|
||||||
|
</UiButton>
|
||||||
|
|
||||||
|
<div class="flex gap-2 justify-end">
|
||||||
|
<Transition name="fade-delay">
|
||||||
|
<UiButton v-if="isLoading" type="button" size="sm" variant="destructive" @click.prevent="handleCancel">
|
||||||
|
{{ $t('ui.action.cancel') }}
|
||||||
|
</UiButton>
|
||||||
|
</Transition>
|
||||||
|
|
||||||
|
<UiButton size="sm" variant="destructive" :disabled="isLoading">
|
||||||
|
<Transition name="fade">
|
||||||
|
<PhCircleNotch v-if="isLoading" size="20" class="animate-spin"/>
|
||||||
|
</Transition>
|
||||||
|
|
||||||
|
{{ $t('ui.action.delete') }}
|
||||||
|
</UiButton>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { PhCircleNotch } from "@phosphor-icons/vue";
|
import { PhCircleNotch } from "@phosphor-icons/vue";
|
||||||
import { useToast } from "~/components/ui/toast";
|
import { useToast } from "~/components/ui/toast";
|
||||||
|
import { ToastLifetime } from "~/components/ui/toast/use-toast";
|
||||||
|
|
||||||
const { resetSkeleton, deleteEventFromSkeleton, cancelLatestRequest } = useCalendar()
|
const { resetSkeleton, deleteEventFromSkeleton, cancelLatestRequest } = useCalendar()
|
||||||
const { isDeleteEventModalOpen, eventSkeleton } = storeToRefs(useCalendar())
|
const { isDeleteEventModalOpen, eventSkeleton } = storeToRefs(useCalendar())
|
||||||
@@ -30,7 +31,7 @@ async function handleAction(): Promise<void> {
|
|||||||
toast({
|
toast({
|
||||||
title: t("entity.calendar.event.deletedToast.title", { event: eventTitle }),
|
title: t("entity.calendar.event.deletedToast.title", { event: eventTitle }),
|
||||||
variant: "success",
|
variant: "success",
|
||||||
duration: 3000
|
duration: ToastLifetime.MEDIUM
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
|
|||||||
147
components/calendar/form/Update.vue
Normal file
147
components/calendar/form/Update.vue
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { Calendar } from "~/models/CalendarConfig";
|
||||||
|
import { PhCircleNotch, PhWrench } from "@phosphor-icons/vue";
|
||||||
|
import type { World } from "~/models/World";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
calendar: Calendar | null,
|
||||||
|
world: World | null,
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const calendarSkeleton = ref<Calendar>({ ...props.calendar } as Calendar)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
calendarSkeleton.value = { ...props.calendar } as Calendar
|
||||||
|
})
|
||||||
|
|
||||||
|
type FormTabs = "global" | "months" | "today"
|
||||||
|
const activeTab = ref<FormTabs>("global")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* === Form Validation ===
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Whether the skeleton has valid month data */
|
||||||
|
const validSkeletonMonths = computed(() => calendarSkeleton.value.months.length > 0)
|
||||||
|
|
||||||
|
/** Whether the skeleton has a valid name */
|
||||||
|
const validSkeletonGeneral = computed(() => calendarSkeleton.value.name)
|
||||||
|
|
||||||
|
/** Whether all the data checks above are a-ok */
|
||||||
|
const validSkeleton = computed(() => validSkeletonGeneral.value && validSkeletonMonths.value)
|
||||||
|
|
||||||
|
/** Send the data to the store for validation */
|
||||||
|
const isUpdatingCalendar = ref<boolean>(false)
|
||||||
|
|
||||||
|
async function handleSubmit() {
|
||||||
|
try {
|
||||||
|
isUpdatingCalendar.value = true
|
||||||
|
await $fetch(`/api/calendars/${calendarSkeleton.value.id}`, { method: "PATCH", body: { ...calendarSkeleton.value, worldId: props.world?.id } })
|
||||||
|
|
||||||
|
emit("on-close")
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
} finally {
|
||||||
|
isUpdatingCalendar.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* === Watch for name changes to display above ===
|
||||||
|
*/
|
||||||
|
const emit = defineEmits<{
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
(e: "on-changed-name", calendarName: string): void
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
(e: "on-close"): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
/** Hook to emit a debounced event for the changed skeleton name */
|
||||||
|
const handleNameChange = useDebounceFn(() => {
|
||||||
|
emit("on-changed-name", calendarSkeleton.value.name)
|
||||||
|
}, 400)
|
||||||
|
|
||||||
|
function handleFormCancel() {
|
||||||
|
emit("on-close")
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<template v-if="calendarSkeleton">
|
||||||
|
<form class="h-full grid grid-rows-[1fr_auto]" @submit.prevent="handleSubmit">
|
||||||
|
<UiTabs v-model:model-value="activeTab">
|
||||||
|
<UiTabsList class="grid w-full grid-cols-1 mb-4">
|
||||||
|
<UiTabsTrigger value="global" class="font-bold">
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<PhWrench size="18" weight="fill" />
|
||||||
|
|
||||||
|
{{ $t('entity.calendar.actionDialog.tabs.general.title') }}
|
||||||
|
</div>
|
||||||
|
</UiTabsTrigger>
|
||||||
|
<!-- <UiTabsTrigger value="months" class="font-bold">
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<PhCalendarDots size="18" weight="fill" />
|
||||||
|
|
||||||
|
{{ $t('entity.calendar.actionDialog.tabs.months.title') }}
|
||||||
|
</div>
|
||||||
|
</UiTabsTrigger>
|
||||||
|
<UiTabsTrigger value="today" class="font-bold">
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<PhAlarm size="18" weight="fill" />
|
||||||
|
|
||||||
|
{{ $t('entity.calendar.actionDialog.tabs.today.title') }}
|
||||||
|
</div>
|
||||||
|
</UiTabsTrigger> -->
|
||||||
|
</UiTabsList>
|
||||||
|
<UiTabsContent value="global" class="grid gap-4">
|
||||||
|
<input
|
||||||
|
id="new-calendar-name"
|
||||||
|
v-model="calendarSkeleton.name"
|
||||||
|
type="text"
|
||||||
|
name="new-calendar-name"
|
||||||
|
required
|
||||||
|
:placeholder="$t('common.title')"
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
|
||||||
|
<div class="-mx-1 px-1 grid gap-3">
|
||||||
|
<UiLabel for="new-calendar-state">
|
||||||
|
{{ $t('ui.contentState.label') }}
|
||||||
|
</UiLabel>
|
||||||
|
|
||||||
|
<InputContentState id="new-calendar-state" v-model="calendarSkeleton.state" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="-mx-1 grid gap-3">
|
||||||
|
<UiLabel for="new-calendar-color">
|
||||||
|
{{ $t('ui.colors.label') }}
|
||||||
|
</UiLabel>
|
||||||
|
|
||||||
|
<InputColor id="new-calendar-color" v-model="calendarSkeleton.color" />
|
||||||
|
</div>
|
||||||
|
</UiTabsContent>
|
||||||
|
<!-- <UiTabsContent value="months">
|
||||||
|
<CalendarInputMonthList v-model:model-value="calendarSkeleton.months" />
|
||||||
|
</UiTabsContent>
|
||||||
|
<UiTabsContent value="today">
|
||||||
|
<CalendarInputTodaySelect v-model:model-value="calendarSkeleton.today" :available-months="calendarSkeleton.months"/>
|
||||||
|
</UiTabsContent> -->
|
||||||
|
</UiTabs>
|
||||||
|
|
||||||
|
<footer class="flex justify-end gap-2 mt-6">
|
||||||
|
<UiButton type="button" variant="destructive" @click="handleFormCancel">
|
||||||
|
{{ $t('ui.action.cancel') }}
|
||||||
|
</UiButton>
|
||||||
|
|
||||||
|
<UiButton type="submit" :disabled="!validSkeleton || isUpdatingCalendar">
|
||||||
|
<Transition name="fade">
|
||||||
|
<PhCircleNotch v-if="isUpdatingCalendar" size="20" class="opacity-50 animate-spin"/>
|
||||||
|
</Transition>
|
||||||
|
|
||||||
|
{{ $t('ui.action.save') }}
|
||||||
|
</UiButton>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { PhAlarm, PhCircleNotch, PhEye, PhEyeClosed, PhMapPinArea, PhPencilSimpleLine, PhTag } from "@phosphor-icons/vue"
|
import { PhAlarm, PhCircleNotch, PhEye, PhEyeClosed, PhMapPinArea, PhPencilSimpleLine, PhTag } from "@phosphor-icons/vue"
|
||||||
import { useToast } from "~/components/ui/toast";
|
import { useToast } from "~/components/ui/toast";
|
||||||
|
import { ToastLifetime } from "~/components/ui/toast/use-toast";
|
||||||
import type { APIError } from "~/models/Errors";
|
import type { APIError } from "~/models/Errors";
|
||||||
|
|
||||||
const emit = defineEmits(["event-updated"])
|
const emit = defineEmits(["event-updated"])
|
||||||
@@ -30,7 +31,7 @@ async function handleAction() {
|
|||||||
toast({
|
toast({
|
||||||
title: t("entity.calendar.event.updatedToast.title", { event: eventSkeleton.value.title }),
|
title: t("entity.calendar.event.updatedToast.title", { event: eventSkeleton.value.title }),
|
||||||
variant: "success",
|
variant: "success",
|
||||||
duration: 3000
|
duration: ToastLifetime.SHORT
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@@ -41,7 +42,7 @@ async function handleAction() {
|
|||||||
title: t("entity.calendar.event.editErrors.toastTitle"),
|
title: t("entity.calendar.event.editErrors.toastTitle"),
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
description: t(`entity.calendar.event.editErrors.${error.path[1]}_${error.code}`),
|
description: t(`entity.calendar.event.editErrors.${error.path[1]}_${error.code}`),
|
||||||
duration: 2000,
|
duration: ToastLifetime.MEDIUM,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
@@ -78,7 +79,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') }}
|
||||||
@@ -94,7 +95,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') }}
|
||||||
@@ -158,7 +159,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">
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ useSortable(monthSortableList, model.value, { animation: 150, handle: ".handle"
|
|||||||
</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">
|
||||||
|
|||||||
@@ -8,19 +8,16 @@ const { isReadOnly } = storeToRefs(useCalendar())
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<header class="pt-4 border-slate-400 dark:border-slate-700 border-b-[1px]">
|
<header class="mt-2 grid gap-4 border-border border-b-[1px]">
|
||||||
<div class="px-6 flex justify-between">
|
<div class="px-8 flex items-center justify-between gap-2">
|
||||||
<menu class="flex items-center gap-2">
|
<menu class="flex items-center gap-2">
|
||||||
<li v-if="!isReadOnly">
|
<li v-if="!isReadOnly">
|
||||||
<CalendarDialogQuickCreateEvent />
|
<LazyCalendarDialogQuickCreateEvent />
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<CalendarMenuToday />
|
<CalendarMenuToday />
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li class="ml-4">
|
||||||
<CalendarMenuNav />
|
|
||||||
</li>
|
|
||||||
<li class="ml-6">
|
|
||||||
<CalendarCurrentDate />
|
<CalendarCurrentDate />
|
||||||
</li>
|
</li>
|
||||||
</menu>
|
</menu>
|
||||||
@@ -29,18 +26,21 @@ const { isReadOnly } = storeToRefs(useCalendar())
|
|||||||
<li>
|
<li>
|
||||||
<UiButton search-slash @click="revealAdvancedSearch()">
|
<UiButton search-slash @click="revealAdvancedSearch()">
|
||||||
<PhMagnifyingGlass size="20" weight="light" />
|
<PhMagnifyingGlass size="20" weight="light" />
|
||||||
{{ $t('entity.advancedSearch.title') }}
|
<span>
|
||||||
|
{{ $t('entity.advancedSearch.title') }}
|
||||||
|
</span>
|
||||||
</UiButton>
|
</UiButton>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<ClientOnly>
|
<CalendarCategoriesCTA />
|
||||||
<CalendarSwitch />
|
</li>
|
||||||
</ClientOnly>
|
<li>
|
||||||
|
<CalendarOptionsCTA />
|
||||||
</li>
|
</li>
|
||||||
</menu>
|
</menu>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ml-6">
|
<div class="ml-8">
|
||||||
<CalendarMenuSubnav />
|
<CalendarMenuSubnav />
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
230
components/calendar/menu/MenuSubnav.vue
Normal file
230
components/calendar/menu/MenuSubnav.vue
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { PhCaretDoubleLeft, PhCaretDoubleRight, PhCaretLeft, PhCaretRight } from "@phosphor-icons/vue"
|
||||||
|
|
||||||
|
const { currentDate } = useCalendar()
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
interface DirectionLabels {
|
||||||
|
pastFar: string
|
||||||
|
pastNear: string
|
||||||
|
futureNear: string
|
||||||
|
futureFar: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const { currentConfig, decrementViewMonth, incrementViewMonth, decrementViewYear, incrementViewYear } =
|
||||||
|
useCalendar()
|
||||||
|
|
||||||
|
const activeDirectionLabels: ComputedRef<DirectionLabels> = computed(() => {
|
||||||
|
switch (currentConfig.viewType) {
|
||||||
|
case "month":
|
||||||
|
return {
|
||||||
|
pastFar: t("entity.calendar.years.prevSingular"),
|
||||||
|
pastNear: t("entity.calendar.months.prevSingular"),
|
||||||
|
futureNear: t("entity.calendar.months.nextSingular"),
|
||||||
|
futureFar: t("entity.calendar.years.nextSingular")
|
||||||
|
}
|
||||||
|
|
||||||
|
case "year":
|
||||||
|
return {
|
||||||
|
pastFar: t("entity.calendar.decades.prevSingular"),
|
||||||
|
pastNear: t("entity.calendar.years.prevSingular"),
|
||||||
|
futureNear: t("entity.calendar.years.nextSingular"),
|
||||||
|
futureFar: t("entity.calendar.decades.nextSingular")
|
||||||
|
}
|
||||||
|
|
||||||
|
case "decade":
|
||||||
|
return {
|
||||||
|
pastFar: t("entity.calendar.centuries.prevSingular"),
|
||||||
|
pastNear: t("entity.calendar.decades.prevSingular"),
|
||||||
|
futureNear: t("entity.calendar.decades.nextSingular"),
|
||||||
|
futureFar: t("entity.calendar.centuries.nextSingular")
|
||||||
|
}
|
||||||
|
|
||||||
|
case "century":
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
pastFar: t("entity.calendar.millenias.prevSingular"),
|
||||||
|
pastNear: t("entity.calendar.centuries.prevSingular"),
|
||||||
|
futureNear: t("entity.calendar.centuries.nextSingular"),
|
||||||
|
futureFar: t("entity.calendar.millenias.nextSingular")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function toPastFar(): void {
|
||||||
|
switch (currentConfig.viewType) {
|
||||||
|
case "month":
|
||||||
|
decrementViewYear()
|
||||||
|
break
|
||||||
|
|
||||||
|
case "year":
|
||||||
|
decrementViewYear(10)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "decade":
|
||||||
|
decrementViewYear(100)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "century":
|
||||||
|
default:
|
||||||
|
decrementViewYear(1000)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toPastNear(): void {
|
||||||
|
switch (currentConfig.viewType) {
|
||||||
|
case "month":
|
||||||
|
decrementViewMonth()
|
||||||
|
break
|
||||||
|
|
||||||
|
case "year":
|
||||||
|
decrementViewYear()
|
||||||
|
break
|
||||||
|
|
||||||
|
case "decade":
|
||||||
|
decrementViewYear(10)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "century":
|
||||||
|
default:
|
||||||
|
decrementViewYear(100)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toFutureNear(): void {
|
||||||
|
switch (currentConfig.viewType) {
|
||||||
|
case "month":
|
||||||
|
incrementViewMonth()
|
||||||
|
break
|
||||||
|
|
||||||
|
case "year":
|
||||||
|
incrementViewYear()
|
||||||
|
break
|
||||||
|
|
||||||
|
case "decade":
|
||||||
|
incrementViewYear(10)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "century":
|
||||||
|
default:
|
||||||
|
incrementViewYear(100)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toFutureFar(): void {
|
||||||
|
switch (currentConfig.viewType) {
|
||||||
|
case "month":
|
||||||
|
incrementViewYear()
|
||||||
|
break
|
||||||
|
|
||||||
|
case "year":
|
||||||
|
incrementViewYear(10)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "decade":
|
||||||
|
incrementViewYear(100)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "century":
|
||||||
|
default:
|
||||||
|
incrementViewYear(1000)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<div class="grid items-end w-40 px-4 py-2 bg-white dark:bg-black border-border border-x-[1px] border-t-[1px] rounded-t-sm text-sm transition-colors">
|
||||||
|
<ClientOnly>
|
||||||
|
<span>{{ currentDate.currentDateTitle }}</span>
|
||||||
|
|
||||||
|
<template #fallback>
|
||||||
|
<span class="inline-block">
|
||||||
|
<UiSkeleton class="h-[19px] w-full rounded-sm" />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</ClientOnly>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<UiTooltipProvider :delay-duration="250">
|
||||||
|
<UiTooltip>
|
||||||
|
<UiTooltipTrigger as-child>
|
||||||
|
<UiButton
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
class="rounded-t-sm rounded-b-none border-b-0"
|
||||||
|
@click="toPastFar()"
|
||||||
|
>
|
||||||
|
<PhCaretDoubleLeft size="18" />
|
||||||
|
</UiButton>
|
||||||
|
</UiTooltipTrigger>
|
||||||
|
<UiTooltipContent>
|
||||||
|
<p>{{ activeDirectionLabels.pastFar }}</p>
|
||||||
|
</UiTooltipContent>
|
||||||
|
</UiTooltip>
|
||||||
|
</UiTooltipProvider>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<UiTooltipProvider :delay-duration="250">
|
||||||
|
<UiTooltip>
|
||||||
|
<UiTooltipTrigger as-child>
|
||||||
|
<UiButton
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
class="rounded-t-sm rounded-b-none border-b-0"
|
||||||
|
@click="toPastNear()"
|
||||||
|
>
|
||||||
|
<PhCaretLeft size="18" />
|
||||||
|
</UiButton>
|
||||||
|
</UiTooltipTrigger>
|
||||||
|
<UiTooltipContent>
|
||||||
|
<p>{{ activeDirectionLabels.pastNear }}</p>
|
||||||
|
</UiTooltipContent>
|
||||||
|
</UiTooltip>
|
||||||
|
</UiTooltipProvider>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<UiTooltipProvider :delay-duration="250">
|
||||||
|
<UiTooltip>
|
||||||
|
<UiTooltipTrigger as-child>
|
||||||
|
<UiButton
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
class="rounded-t-sm rounded-b-none border-b-0"
|
||||||
|
@click="toFutureNear()"
|
||||||
|
>
|
||||||
|
<PhCaretRight size="18" />
|
||||||
|
</UiButton>
|
||||||
|
</UiTooltipTrigger>
|
||||||
|
<UiTooltipContent>
|
||||||
|
<p>{{ activeDirectionLabels.futureNear }}</p>
|
||||||
|
</UiTooltipContent>
|
||||||
|
</UiTooltip>
|
||||||
|
</UiTooltipProvider>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<UiTooltipProvider :delay-duration="250">
|
||||||
|
<UiTooltip>
|
||||||
|
<UiTooltipTrigger as-child>
|
||||||
|
<UiButton
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
class="rounded-t-sm rounded-b-none border-b-0"
|
||||||
|
@click="toFutureFar()"
|
||||||
|
>
|
||||||
|
<PhCaretDoubleRight size="18" />
|
||||||
|
</UiButton>
|
||||||
|
</UiTooltipTrigger>
|
||||||
|
<UiTooltipContent>
|
||||||
|
<p>{{ activeDirectionLabels.futureFar }}</p>
|
||||||
|
</UiTooltipContent>
|
||||||
|
</UiTooltip>
|
||||||
|
</UiTooltipProvider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -26,9 +26,12 @@ 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"
|
||||||
|
|
||||||
const { isAdvancedSearchOpen, allEvents } = storeToRefs(useCalendar())
|
const { isAdvancedSearchOpen, allEvents, categories } = storeToRefs(useCalendar())
|
||||||
const { characters } = storeToRefs(useCharacters())
|
const { characters } = storeToRefs(useCharacters())
|
||||||
|
|
||||||
|
const searchInput = shallowRef<HTMLInputElement>()
|
||||||
|
useFocus(searchInput, { initialValue: true })
|
||||||
|
|
||||||
const searchQuery = ref<string>("")
|
const searchQuery = ref<string>("")
|
||||||
// const searchEnough = computed<boolean>(() => searchQuery.value.length >= 2)
|
// const searchEnough = computed<boolean>(() => searchQuery.value.length >= 2)
|
||||||
|
|
||||||
@@ -204,8 +207,7 @@ watch([currentPage, selectedEntity], () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Compute categories based on current selectedEntity
|
// Compute categories based on current selectedEntity
|
||||||
const { data: resCategories } = await useFetch("/api/calendars/categories/query")
|
const currentCategories = computed(() => categories.value)
|
||||||
const currentCategories = ref<Category[]>(resCategories.value?.data as Category[])
|
|
||||||
|
|
||||||
const selectedCategories = ref<(Category)[]>([])
|
const selectedCategories = ref<(Category)[]>([])
|
||||||
const categoryFilterOpened = ref<boolean>(false)
|
const categoryFilterOpened = ref<boolean>(false)
|
||||||
@@ -281,6 +283,7 @@ function handleCategoryUnselect(e: Category) {
|
|||||||
<div class="relative w-full h-fit">
|
<div class="relative w-full h-fit">
|
||||||
<UiInput
|
<UiInput
|
||||||
id="search"
|
id="search"
|
||||||
|
ref="searchInput"
|
||||||
v-model:model-value="searchQuery"
|
v-model:model-value="searchQuery"
|
||||||
type="text"
|
type="text"
|
||||||
:placeholder="$t('entity.advancedSearch.ctaPlaceholder')"
|
:placeholder="$t('entity.advancedSearch.ctaPlaceholder')"
|
||||||
@@ -321,7 +324,7 @@ function handleCategoryUnselect(e: Category) {
|
|||||||
<ComboboxRoot
|
<ComboboxRoot
|
||||||
v-model="selectedCategories"
|
v-model="selectedCategories"
|
||||||
v-model:open="categoryFilterOpened"
|
v-model:open="categoryFilterOpened"
|
||||||
v-model:searchTerm="searchCategory"
|
v-model:search-term="searchCategory"
|
||||||
class="grow flex items-center gap-y-1 pr-2"
|
class="grow flex items-center gap-y-1 pr-2"
|
||||||
>
|
>
|
||||||
<ComboboxAnchor as-child>
|
<ComboboxAnchor as-child>
|
||||||
@@ -342,7 +345,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 />
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -21,16 +21,16 @@ const dateDuration = computed<string | null>(() => props.event.endDate ? getRela
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
class="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-3 px-4 cursor-pointer transition-colors"
|
||||||
:class="cn(
|
:class="cn(
|
||||||
event.category ? `event-${event.category.color}` : '',
|
event.category ? `element-${event.category.color}` : '',
|
||||||
{
|
{
|
||||||
'pt-4': 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">
|
<h2 class="font-bold text-lg underline-offset-4 group-hover:underline">
|
||||||
{{ event.title }}
|
{{ event.title }}
|
||||||
</h2>
|
</h2>
|
||||||
<div v-if="event.wiki">
|
<div v-if="event.wiki">
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ const pagedResults = computed(() => sortedResults.value.slice(props.startAt, pro
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ul class="grid gap-4">
|
<ul class="grid gap-4 pl-8 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" />
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { 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"
|
||||||
|
|
||||||
import CalendarEventButton from "../../CalendarEvent.vue"
|
import CalendarEventButton from "../../event/Event.vue"
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
date: RPGDate
|
date: RPGDate
|
||||||
@@ -73,7 +73,7 @@ const eventsNotDisplayed: ComputedRef<number> = computed<number>(() => eventsFo
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
ref="calendarTile"
|
ref="calendarTile"
|
||||||
class="tile relative text-xs p-2 border-slate-400 dark:border-slate-700 dark:bg-black transition-colors"
|
class="tile relative text-xs p-2"
|
||||||
: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
|
||||||
@@ -149,14 +149,11 @@ const eventsNotDisplayed: ComputedRef<number> = computed<number>(() => eventsFo
|
|||||||
</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 {
|
||||||
&:not(:nth-child(10n)) {
|
border-right-width: 1px;
|
||||||
border-right-width: 1px;
|
border-bottom-width: 1px;
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(n + 11) {
|
|
||||||
border-top-width: 1px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.event-enter-active {
|
.event-enter-active {
|
||||||
|
|||||||
21
components/global/AddCard.vue
Normal file
21
components/global/AddCard.vue
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { PhPlusCircle } from "@phosphor-icons/vue";
|
||||||
|
|
||||||
|
const emit = defineEmits(["on-click"])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UiCard
|
||||||
|
class="h-full lg:w-fit transition-all bg-transparent hover:text-slate-900 text-slate-500 dark:hover:text-slate-100 dark:text-slate-500 dark:focus-within:outline-gray-900 hover:-translate-y-0"
|
||||||
|
has-click
|
||||||
|
@on-click="emit('on-click')"
|
||||||
|
>
|
||||||
|
<UiCardHeader class="h-full justify-center items-center text-center gap-0">
|
||||||
|
<PhPlusCircle size="38" weight="fill" />
|
||||||
|
|
||||||
|
<UiCardTitle class="text-xl">
|
||||||
|
<slot />
|
||||||
|
</UiCardTitle>
|
||||||
|
</UiCardHeader>
|
||||||
|
</UiCard>
|
||||||
|
</template>
|
||||||
60
components/global/Breadcrumb.vue
Normal file
60
components/global/Breadcrumb.vue
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { PhCaretRight } from "@phosphor-icons/vue";
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
export type BreadcrumbItem = {
|
||||||
|
label?: string;
|
||||||
|
translateKey?: string;
|
||||||
|
to?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
items?: BreadcrumbItem[];
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UiBreadcrumb>
|
||||||
|
<UiBreadcrumbList>
|
||||||
|
<UiBreadcrumbItem v-if="route.path !== '/my'">
|
||||||
|
<UiBreadcrumbLink as-child>
|
||||||
|
<NuxtLink to="/my">
|
||||||
|
{{ $t("breadcrumbs.profile") }}
|
||||||
|
</NuxtLink>
|
||||||
|
</UiBreadcrumbLink>
|
||||||
|
</UiBreadcrumbItem>
|
||||||
|
<UiBreadcrumbItem v-else>
|
||||||
|
<UiBreadcrumbPage>
|
||||||
|
{{ $t("breadcrumbs.profile") }}
|
||||||
|
</UiBreadcrumbPage>
|
||||||
|
</UiBreadcrumbItem>
|
||||||
|
|
||||||
|
<template v-for="(item, index) in items" :key="index">
|
||||||
|
<UiBreadcrumbSeparator>
|
||||||
|
<PhCaretRight />
|
||||||
|
</UiBreadcrumbSeparator>
|
||||||
|
<UiBreadcrumbItem>
|
||||||
|
<UiBreadcrumbLink v-if="item.to" as-child>
|
||||||
|
<NuxtLink :to="item.to">
|
||||||
|
<template v-if="item.label">
|
||||||
|
{{ item.label }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ $t(`breadcrumbs.${item.translateKey}`) }}
|
||||||
|
</template>
|
||||||
|
</NuxtLink>
|
||||||
|
</UiBreadcrumbLink>
|
||||||
|
<UiBreadcrumbPage v-else>
|
||||||
|
<template v-if="item.label">
|
||||||
|
{{ item.label }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ $t(`breadcrumbs.${item.translateKey}`) }}
|
||||||
|
</template>
|
||||||
|
</UiBreadcrumbPage>
|
||||||
|
</UiBreadcrumbItem>
|
||||||
|
</template>
|
||||||
|
</UiBreadcrumbList>
|
||||||
|
</UiBreadcrumb>
|
||||||
|
</template>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
type HeadingLevel = "h1" | "h2" | "h3"
|
type HeadingLevel = "h0" | "h1" | "h2" | "h3"
|
||||||
|
|
||||||
interface HeadingProps {
|
interface HeadingProps {
|
||||||
level?: HeadingLevel
|
level?: HeadingLevel
|
||||||
@@ -11,13 +11,16 @@ withDefaults(defineProps<HeadingProps>(), {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<h1 v-if="level === 'h1'" class="text-4xl font-bold flex">
|
<h1 v-if="level === 'h0'" class="text-4xl md:text-6xl font-bold flex">
|
||||||
<slot />
|
<slot />
|
||||||
</h1>
|
</h1>
|
||||||
<h2 v-else-if="level === 'h2'" class="text-2xl font-bold flex">
|
<h1 v-else-if="level === 'h1'" class="text-2xl md:text-4xl font-bold flex">
|
||||||
|
<slot />
|
||||||
|
</h1>
|
||||||
|
<h2 v-else-if="level === 'h2'" class="text-xl md:text-2xl font-bold flex">
|
||||||
<slot />
|
<slot />
|
||||||
</h2>
|
</h2>
|
||||||
<h3 v-if="level === 'h3'" class="text-xl font-bold flex">
|
<h3 v-if="level === 'h3'" class="text-lg md:text-xl font-bold flex">
|
||||||
<slot />
|
<slot />
|
||||||
</h3>
|
</h3>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
21
components/global/LoadingCard.vue
Normal file
21
components/global/LoadingCard.vue
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
const { lineNb = 3 } = defineProps<{
|
||||||
|
lineNb?: number
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UiCard class="w-full h-full flex flex-col">
|
||||||
|
<UiCardHeader>
|
||||||
|
<UiCardTitle>
|
||||||
|
<UiSkeleton class="w-2/3 max-w-full h-8" />
|
||||||
|
</UiCardTitle>
|
||||||
|
</UiCardHeader>
|
||||||
|
<UiCardContent class="grow grid gap-2">
|
||||||
|
<UiSkeleton class="w-full max-w-full h-6" />
|
||||||
|
<template v-for="(l, i) in lineNb" :key="i">
|
||||||
|
<UiSkeleton class="max-w-full h-6" :style="`width: ${Math.floor(Math.random() * 100) + 1}%`" />
|
||||||
|
</template>
|
||||||
|
</UiCardContent>
|
||||||
|
</UiCard>
|
||||||
|
</template>
|
||||||
@@ -1,17 +1,38 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { type RPGColor, rpgColors } from "~/models/Color";
|
import { type RPGColor, rpgColors } from "~/models/Color";
|
||||||
|
|
||||||
const model = defineModel<RPGColor>()
|
const { id, theme = "normal", position = "popper" } = defineProps<{
|
||||||
|
id: string
|
||||||
|
theme?: "normal" | "subtle"
|
||||||
|
position?: "item-aligned" | "popper" | undefined
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const model = defineModel<RPGColor>({ default: "white" });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<UiSelect v-model="model">
|
<UiSelect v-model="model">
|
||||||
<UiSelectTrigger>
|
<UiSelectTrigger :id :class="cn({ 'h-9': theme === 'subtle' })">
|
||||||
<UiSelectValue :placeholder="$t('ui.colors.selectOne')" />
|
<UiSelectValue
|
||||||
|
:placeholder="$t('ui.colors.selectOne')"
|
||||||
|
class="bgc"
|
||||||
|
:class="cn(`element-${model}`)"
|
||||||
|
/>
|
||||||
</UiSelectTrigger>
|
</UiSelectTrigger>
|
||||||
<UiSelectContent>
|
<UiSelectContent :position>
|
||||||
<UiSelectGroup>
|
<UiSelectGroup>
|
||||||
<UiSelectItem v-for="color in rpgColors" :key="color" :value="color">
|
<UiSelectItem
|
||||||
|
v-for="color in rpgColors"
|
||||||
|
:key="color"
|
||||||
|
:value="color"
|
||||||
|
class="bgc"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
`element-${color}`,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
{{ $t(`ui.colors.${color}`) }}
|
{{ $t(`ui.colors.${color}`) }}
|
||||||
</UiSelectItem>
|
</UiSelectItem>
|
||||||
</UiSelectGroup>
|
</UiSelectGroup>
|
||||||
|
|||||||
30
components/global/input/ContentState.vue
Normal file
30
components/global/input/ContentState.vue
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { contentStates, type ContentState } from "~/models/Entity";
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
id: string
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const model = defineModel<ContentState>({ default: "draft" });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UiSelect v-model="model">
|
||||||
|
<UiSelectTrigger :id>
|
||||||
|
<UiSelectValue
|
||||||
|
:placeholder="$t('ui.contentState.selectOne')"
|
||||||
|
/>
|
||||||
|
</UiSelectTrigger>
|
||||||
|
<UiSelectContent position="popper">
|
||||||
|
<UiSelectGroup>
|
||||||
|
<UiSelectItem
|
||||||
|
v-for="state in contentStates"
|
||||||
|
:key="state"
|
||||||
|
:value="state"
|
||||||
|
>
|
||||||
|
{{ $t(`ui.contentState.${state}`) }}
|
||||||
|
</UiSelectItem>
|
||||||
|
</UiSelectGroup>
|
||||||
|
</UiSelectContent>
|
||||||
|
</UiSelect>
|
||||||
|
</template>
|
||||||
@@ -1,21 +1,30 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { PhCompass, PhList } from "@phosphor-icons/vue"
|
import { PhCompass, PhGlobeHemisphereEast, PhHurricane, PhList } from "@phosphor-icons/vue"
|
||||||
import type { SidebarMenuActionType } from "./SidebarProps";
|
import type { SidebarMenuActionType, SidebarMenuIcon } from "./SidebarProps";
|
||||||
|
|
||||||
const { revealAdvancedSearch } = useCalendar()
|
const { revealAdvancedSearch } = useCalendar()
|
||||||
const { currentMenu } = storeToRefs(useUiStore())
|
const { currentMenu } = storeToRefs(useUiStore())
|
||||||
|
|
||||||
// const user = useSupabaseUser()
|
|
||||||
|
|
||||||
function handleMenuItemAction(actionType: SidebarMenuActionType) {
|
function handleMenuItemAction(actionType: SidebarMenuActionType) {
|
||||||
if (actionType === "event-search") {
|
if (actionType === "event-search") {
|
||||||
revealAdvancedSearch()
|
revealAdvancedSearch()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function computeMenuItemIcon(iconString: SidebarMenuIcon) {
|
||||||
|
switch (iconString) {
|
||||||
|
case "universe":
|
||||||
|
return PhHurricane
|
||||||
|
case "world":
|
||||||
|
return PhGlobeHemisphereEast
|
||||||
|
default:
|
||||||
|
return PhCompass
|
||||||
|
}
|
||||||
|
}
|
||||||
</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">
|
<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">
|
||||||
<menu class="flex flex-col gap-4">
|
<menu class="flex flex-col gap-4">
|
||||||
<li class="mb-12">
|
<li class="mb-12">
|
||||||
<UiButton variant="ghost" size="icon" class="rounded-full" @click="console.log">
|
<UiButton variant="ghost" size="icon" class="rounded-full" @click="console.log">
|
||||||
@@ -42,27 +51,64 @@ function handleMenuItemAction(actionType: SidebarMenuActionType) {
|
|||||||
</UiTooltipProvider>
|
</UiTooltipProvider>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li v-for="(item, i) in currentMenu" :key="i">
|
<ClientOnly>
|
||||||
<UiTooltipProvider :delay-duration="50">
|
<li v-for="(item, i) in currentMenu" :key="i">
|
||||||
<UiTooltip>
|
<UiTooltipProvider :delay-duration="50">
|
||||||
<UiTooltipTrigger as-child>
|
<UiTooltip>
|
||||||
<UiButton v-if="item.to" variant="ghost" size="icon" class="rounded-full" as-child>
|
<UiTooltipTrigger as-child>
|
||||||
<RouterLink :to="item.to">
|
<UiButton
|
||||||
<component :is="item.phIcon" size="24" weight="fill" />
|
v-if="item.to"
|
||||||
</RouterLink>
|
variant="ghost"
|
||||||
</UiButton>
|
size="icon"
|
||||||
<UiButton v-if="item.action" variant="ghost" size="icon" class="rounded-full" @click="handleMenuItemAction(item.action!)">
|
class="rounded-full"
|
||||||
<component :is="item.phIcon" size="24" weight="fill" />
|
as-child
|
||||||
</UiButton>
|
>
|
||||||
</UiTooltipTrigger>
|
<RouterLink :to="item.to">
|
||||||
<UiTooltipContent :side="'right'" :side-offset="6">
|
<component :is="computeMenuItemIcon(item.phIcon)" size="24" :weight="item.phIconWeight || 'fill'" />
|
||||||
<p>{{ item.tooltip }}</p>
|
</RouterLink>
|
||||||
</UiTooltipContent>
|
</UiButton>
|
||||||
</UiTooltip>
|
<UiButton
|
||||||
</UiTooltipProvider>
|
v-if="item.action"
|
||||||
</li>
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
class="rounded-full"
|
||||||
|
@click="handleMenuItemAction(item.action!)"
|
||||||
|
>
|
||||||
|
<component :is="computeMenuItemIcon(item.phIcon)" size="24" :weight="item.phIconWeight || 'fill'" />
|
||||||
|
</UiButton>
|
||||||
|
</UiTooltipTrigger>
|
||||||
|
<UiTooltipContent :side="'right'" :side-offset="6">
|
||||||
|
<p>{{ item.tooltip }}</p>
|
||||||
|
</UiTooltipContent>
|
||||||
|
</UiTooltip>
|
||||||
|
</UiTooltipProvider>
|
||||||
|
</li>
|
||||||
|
</ClientOnly>
|
||||||
</menu>
|
</menu>
|
||||||
|
|
||||||
<UserCTA />
|
<UserCTA />
|
||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
nav {
|
||||||
|
position: relative;
|
||||||
|
isolation: isolate;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
display: block;
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
inset-inline: 0;
|
||||||
|
height: 25rem;
|
||||||
|
max-height: 100%;
|
||||||
|
background-image: url("/images/sidebar.png");
|
||||||
|
background-position: bottom;
|
||||||
|
background-size: cover;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
mask-image: linear-gradient(to top, black 25%, transparent 50%, transparent);
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import type { ShallowRef } from "vue"
|
|
||||||
|
|
||||||
export type SidebarMenuActionType = "event-search"
|
export type SidebarMenuActionType = "event-search"
|
||||||
|
|
||||||
|
export type SidebarMenuIcon = "universe" | "world"
|
||||||
|
|
||||||
export interface SidebarMenuItem {
|
export interface SidebarMenuItem {
|
||||||
phIcon: ShallowRef // use shallowRef to build phIcon
|
phIcon: SidebarMenuIcon,
|
||||||
|
phIconWeight?: "regular" | "light" | "fill" | "duotone" | "bold" | "thin"
|
||||||
|
highlight?: boolean
|
||||||
tooltip: string
|
tooltip: string
|
||||||
action?: SidebarMenuActionType
|
action?: SidebarMenuActionType
|
||||||
to?: string
|
to?: string
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from "vue"
|
import { computed } from "vue"
|
||||||
|
|
||||||
import { PhCheckCircle, PhGear, PhUser, PhLaptop, PhMoon, PhPalette, PhSignIn, PhSignOut, PhSun, PhTranslate, PhUserCircle } from "@phosphor-icons/vue"
|
import { PhCheckCircle, PhUser, PhLaptop, PhMoon, PhPalette, PhSignIn, PhSignOut, PhSun, PhTranslate, PhUserCircle } from "@phosphor-icons/vue"
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -60,7 +60,7 @@ function pushRoute(to: AvailableRoutes) {
|
|||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<UiDropdownMenu v-model:open="menuOpened">
|
<UiDropdownMenu v-model:open="menuOpened">
|
||||||
<UiDropdownMenuTrigger>
|
<UiDropdownMenuTrigger>
|
||||||
<UiAvatar v-if="user" class="cursor-pointer">
|
<UiAvatar v-if="user" id="user-avatar" class="ring-[.2rem] ring-indigo-700 dark:ring-neutral-900 cursor-pointer">
|
||||||
<UiAvatarImage
|
<UiAvatarImage
|
||||||
:src="userMeta?.avatar_url"
|
:src="userMeta?.avatar_url"
|
||||||
:alt="userMeta?.full_name"
|
:alt="userMeta?.full_name"
|
||||||
@@ -70,7 +70,7 @@ function pushRoute(to: AvailableRoutes) {
|
|||||||
{{ $t('ui.sidebarMenu.avatarFallback') }}
|
{{ $t('ui.sidebarMenu.avatarFallback') }}
|
||||||
</UiAvatarFallback>
|
</UiAvatarFallback>
|
||||||
</UiAvatar>
|
</UiAvatar>
|
||||||
<UiButton v-else variant="outline" size="icon" class="rounded-full">
|
<UiButton v-else variant="outline" size="icon" class="rounded-full border-indigo-200 bg-indigo-700 dark:border-slate-300 dark:bg-neutral-950 dark:hover:bg-slate-50 dark:hover:text-slate-950 cursor-pointer">
|
||||||
<PhUserCircle size="24" />
|
<PhUserCircle size="24" />
|
||||||
</UiButton>
|
</UiButton>
|
||||||
</UiDropdownMenuTrigger>
|
</UiDropdownMenuTrigger>
|
||||||
@@ -186,12 +186,12 @@ function pushRoute(to: AvailableRoutes) {
|
|||||||
<UiDropdownMenuSeparator />
|
<UiDropdownMenuSeparator />
|
||||||
|
|
||||||
<template v-if="user">
|
<template v-if="user">
|
||||||
<UiDropdownMenuItem class="flex gap-[.5ch] items-center rounded-none" @click="pushRoute('/my/settings')">
|
<!-- <UiDropdownMenuItem class="flex gap-[.5ch] items-center rounded-none" @click="pushRoute('/my/settings')">
|
||||||
<PhGear size="20" weight="fill" />
|
<PhGear size="20" weight="fill" />
|
||||||
<span>
|
<span>
|
||||||
{{ $t('ui.sidebarMenu.account') }}
|
{{ $t('ui.sidebarMenu.account') }}
|
||||||
</span>
|
</span>
|
||||||
</UiDropdownMenuItem>
|
</UiDropdownMenuItem> -->
|
||||||
<UiDropdownMenuItem class="flex gap-[.5ch] items-center rounded-none" @click="handleLogout">
|
<UiDropdownMenuItem class="flex gap-[.5ch] items-center rounded-none" @click="handleLogout">
|
||||||
<PhSignOut size="20" weight="fill" />
|
<PhSignOut size="20" weight="fill" />
|
||||||
<span>
|
<span>
|
||||||
|
|||||||
@@ -25,13 +25,13 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|||||||
<template>
|
<template>
|
||||||
<AlertDialogPortal>
|
<AlertDialogPortal>
|
||||||
<AlertDialogOverlay
|
<AlertDialogOverlay
|
||||||
class="fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
|
class="fixed inset-0 z-50 bg-black/60 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
|
||||||
/>
|
/>
|
||||||
<AlertDialogContent
|
<AlertDialogContent
|
||||||
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 bg-background 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-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-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% sm:rounded-lg',
|
||||||
props.class,
|
props.class,
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
13
components/ui/breadcrumb/Breadcrumb.vue
Normal file
13
components/ui/breadcrumb/Breadcrumb.vue
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<nav aria-label="breadcrumb" :class="props.class">
|
||||||
|
<slot />
|
||||||
|
</nav>
|
||||||
|
</template>
|
||||||
22
components/ui/breadcrumb/BreadcrumbEllipsis.vue
Normal file
22
components/ui/breadcrumb/BreadcrumbEllipsis.vue
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { MoreHorizontal } from "lucide-vue-next"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span
|
||||||
|
role="presentation"
|
||||||
|
aria-hidden="true"
|
||||||
|
:class="cn('flex h-9 w-9 items-center justify-center', props.class)"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<MoreHorizontal class="h-4 w-4" />
|
||||||
|
</slot>
|
||||||
|
<span class="sr-only">More</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
16
components/ui/breadcrumb/BreadcrumbItem.vue
Normal file
16
components/ui/breadcrumb/BreadcrumbItem.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<li
|
||||||
|
:class="cn('inline-flex items-center gap-1.5', props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
20
components/ui/breadcrumb/BreadcrumbLink.vue
Normal file
20
components/ui/breadcrumb/BreadcrumbLink.vue
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { PrimitiveProps } from "radix-vue"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Primitive } from "radix-vue"
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<PrimitiveProps & { class?: HTMLAttributes["class"] }>(), {
|
||||||
|
as: "a",
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Primitive
|
||||||
|
:as="as"
|
||||||
|
:as-child="asChild"
|
||||||
|
:class="cn('transition-colors hover:text-foreground', props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</Primitive>
|
||||||
|
</template>
|
||||||
16
components/ui/breadcrumb/BreadcrumbList.vue
Normal file
16
components/ui/breadcrumb/BreadcrumbList.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ol
|
||||||
|
:class="cn('flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5', props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</ol>
|
||||||
|
</template>
|
||||||
19
components/ui/breadcrumb/BreadcrumbPage.vue
Normal file
19
components/ui/breadcrumb/BreadcrumbPage.vue
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span
|
||||||
|
role="link"
|
||||||
|
aria-disabled="true"
|
||||||
|
aria-current="page"
|
||||||
|
:class="cn('font-normal text-foreground', props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
21
components/ui/breadcrumb/BreadcrumbSeparator.vue
Normal file
21
components/ui/breadcrumb/BreadcrumbSeparator.vue
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { ChevronRight } from "lucide-vue-next"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<li
|
||||||
|
role="presentation"
|
||||||
|
aria-hidden="true"
|
||||||
|
:class="cn('[&>svg]:w-3 [&>svg]:h-3', props.class)"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<ChevronRight />
|
||||||
|
</slot>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
7
components/ui/breadcrumb/index.ts
Normal file
7
components/ui/breadcrumb/index.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export { default as Breadcrumb } from "./Breadcrumb.vue"
|
||||||
|
export { default as BreadcrumbEllipsis } from "./BreadcrumbEllipsis.vue"
|
||||||
|
export { default as BreadcrumbItem } from "./BreadcrumbItem.vue"
|
||||||
|
export { default as BreadcrumbLink } from "./BreadcrumbLink.vue"
|
||||||
|
export { default as BreadcrumbList } from "./BreadcrumbList.vue"
|
||||||
|
export { default as BreadcrumbPage } from "./BreadcrumbPage.vue"
|
||||||
|
export { default as BreadcrumbSeparator } from "./BreadcrumbSeparator.vue"
|
||||||
@@ -20,15 +20,15 @@ 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"
|
||||||
>
|
>
|
||||||
<span class="-mt-[2px]">CTRL + :</span>
|
<kbd class="-mt-[2px]">CTRL + :</kbd>
|
||||||
</span>
|
</span>
|
||||||
</Primitive>
|
</Primitive>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ 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: {
|
||||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||||
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||||
outline: "border border-input hover:bg-accent hover:text-accent-foreground",
|
outline: "border border-input hover:bg-accent hover:text-accent-foreground",
|
||||||
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
secondary: "bg-secondary text-secondary-foreground hover:bg-primary/20",
|
||||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||||
link: "text-primary underline-offset-4 hover:underline"
|
link: "text-primary underline-offset-4 hover:underline"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,15 +5,18 @@ import { cn } from "@/lib/utils"
|
|||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
class?: HTMLAttributes["class"]
|
class?: HTMLAttributes["class"]
|
||||||
link?: string
|
link?: string
|
||||||
|
hasClick?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits(["on-click"])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<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 outline outline-2 outline-offset-4 outline-transparent hover:-translate-y-[.2rem]':
|
||||||
props.link
|
props.link || props.hasClick
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
@@ -22,7 +25,11 @@ const props = defineProps<{
|
|||||||
<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="absolute inset-0 z-10 focus-visible:outline-hidden"
|
||||||
/>
|
/>
|
||||||
|
<button
|
||||||
|
v-if="props.hasClick"
|
||||||
|
class="absolute inset-0 z-10 focus-visible:outline-hidden"
|
||||||
|
@click="emit('on-click')" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const props = defineProps<{
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="cn('flex items-center p-6 pt-0', props.class)">
|
<div :class="cn('flex items-center p-6 border-t-2 border-t-slate-900', props.class)">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ 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 h-4 w-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">
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const forwardedProps = useForwardProps(delegatedProps)
|
|||||||
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
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -27,13 +27,13 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|||||||
<template>
|
<template>
|
||||||
<DialogPortal>
|
<DialogPortal>
|
||||||
<DialogOverlay
|
<DialogOverlay
|
||||||
class="fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
|
class="fixed inset-0 z-50 bg-black/60 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
|
||||||
/>
|
/>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
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 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% sm:rounded-lg',
|
||||||
props.class
|
props.class
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
@@ -41,7 +41,7 @@ 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" />
|
<X class="w-4 h-4" />
|
||||||
<span class="sr-only">Close</span>
|
<span class="sr-only">Close</span>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|||||||
<template>
|
<template>
|
||||||
<DialogPortal>
|
<DialogPortal>
|
||||||
<DialogOverlay
|
<DialogOverlay
|
||||||
class="fixed inset-0 z-50 grid place-items-center overflow-y-auto bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
|
class="fixed inset-0 z-50 grid place-items-center overflow-y-auto bg-black/60 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
|
||||||
>
|
>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
:class="
|
:class="
|
||||||
|
|||||||
13
components/ui/dropdown-menu/DropdownMenuArrow.vue
Normal file
13
components/ui/dropdown-menu/DropdownMenuArrow.vue
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { DropdownMenuArrow, type DropdownMenuArrowProps, useForwardProps } from "radix-vue"
|
||||||
|
|
||||||
|
const props = defineProps<DropdownMenuArrowProps>()
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(props)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DropdownMenuArrow class="fill-border" v-bind="forwardedProps">
|
||||||
|
<slot />
|
||||||
|
</DropdownMenuArrow>
|
||||||
|
</template>
|
||||||
@@ -27,7 +27,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 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
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -28,7 +28,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 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
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import {
|
|||||||
type DropdownMenuSubTriggerProps,
|
type DropdownMenuSubTriggerProps,
|
||||||
useForwardProps
|
useForwardProps
|
||||||
} from "radix-vue"
|
} from "radix-vue"
|
||||||
import { ChevronRight } from "lucide-vue-next"
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
import { PhCaretLeft, PhCaretRight } from "@phosphor-icons/vue";
|
||||||
|
|
||||||
const props = defineProps<DropdownMenuSubTriggerProps & { class?: HTMLAttributes["class"] }>()
|
const props = defineProps<DropdownMenuSubTriggerProps & { class?: HTMLAttributes["class"], arrowDirection?: "left" | "right" }>()
|
||||||
|
|
||||||
const delegatedProps = computed(() => {
|
const delegatedProps = computed(() => {
|
||||||
const { class: _, ...delegated } = props
|
const { class: _, ...delegated } = props
|
||||||
@@ -24,12 +24,22 @@ 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
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<slot />
|
<template v-if="props.arrowDirection === 'left'">
|
||||||
<ChevronRight class="ml-auto h-4 w-4" />
|
<PhCaretLeft class="mr-auto h-4 w-4" />
|
||||||
|
|
||||||
|
<span class="inline-block">
|
||||||
|
<slot />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<slot />
|
||||||
|
|
||||||
|
<PhCaretRight class="ml-auto h-4 w-4" />
|
||||||
|
</template>
|
||||||
</DropdownMenuSubTrigger>
|
</DropdownMenuSubTrigger>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export { DropdownMenuPortal } from "radix-vue"
|
export { DropdownMenuPortal } from "radix-vue"
|
||||||
|
|
||||||
export { default as DropdownMenu } from "./DropdownMenu.vue"
|
export { default as DropdownMenu } from "./DropdownMenu.vue"
|
||||||
|
export { default as DropdownMenuArrow } from "./DropdownMenuArrow.vue"
|
||||||
export { default as DropdownMenuTrigger } from "./DropdownMenuTrigger.vue"
|
export { default as DropdownMenuTrigger } from "./DropdownMenuTrigger.vue"
|
||||||
export { default as DropdownMenuContent } from "./DropdownMenuContent.vue"
|
export { default as DropdownMenuContent } from "./DropdownMenuContent.vue"
|
||||||
export { default as DropdownMenuGroup } from "./DropdownMenuGroup.vue"
|
export { default as DropdownMenuGroup } from "./DropdownMenuGroup.vue"
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const delegatedProps = computed(() => {
|
|||||||
v-bind="delegatedProps"
|
v-bind="delegatedProps"
|
||||||
:class="
|
:class="
|
||||||
cn(
|
cn(
|
||||||
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer',
|
||||||
props.class,
|
props.class,
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|||||||
v-bind="{ ...forwarded, ...$attrs }"
|
v-bind="{ ...forwarded, ...$attrs }"
|
||||||
:class="
|
:class="
|
||||||
cn(
|
cn(
|
||||||
'z-50 w-72 rounded-md border bg-popover p-4 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 w-72 rounded-md border bg-popover p-4 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',
|
||||||
props.class
|
props.class
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -36,9 +36,10 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|||||||
<SelectPortal>
|
<SelectPortal>
|
||||||
<SelectContent
|
<SelectContent
|
||||||
v-bind="{ ...forwarded, ...$attrs }"
|
v-bind="{ ...forwarded, ...$attrs }"
|
||||||
|
style="max-height: var(--radix-select-content-available-height)"
|
||||||
:class="
|
:class="
|
||||||
cn(
|
cn(
|
||||||
'relative z-50 max-h-96 min-w-32 overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md 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',
|
'select-content relative z-50 max-h-96 min-w-32 overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md 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',
|
||||||
position === 'popper' &&
|
position === 'popper' &&
|
||||||
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
||||||
props.class
|
props.class
|
||||||
@@ -51,7 +52,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|||||||
cn(
|
cn(
|
||||||
'p-1',
|
'p-1',
|
||||||
position === 'popper' &&
|
position === 'popper' &&
|
||||||
'h-[--radix-select-trigger-height] w-full min-w-[--radix-select-trigger-width]'
|
'h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width)'
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const forwardedProps = useForwardProps(delegatedProps)
|
|||||||
v-bind="forwardedProps"
|
v-bind="forwardedProps"
|
||||||
:class="
|
:class="
|
||||||
cn(
|
cn(
|
||||||
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50',
|
||||||
props.class
|
props.class
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const forwardedProps = useForwardProps(delegatedProps)
|
|||||||
v-bind="forwardedProps"
|
v-bind="forwardedProps"
|
||||||
:class="
|
:class="
|
||||||
cn(
|
cn(
|
||||||
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
|
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background dark:bg-slate-950 contrast-more:dark:bg-slate-900 px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
props.class
|
props.class
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|||||||
<SwitchRoot
|
<SwitchRoot
|
||||||
v-bind="forwarded"
|
v-bind="forwarded"
|
||||||
:class="cn(
|
:class="cn(
|
||||||
'peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
|
'peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
|
||||||
props.class,
|
props.class,
|
||||||
)"
|
)"
|
||||||
>
|
>
|
||||||
|
|||||||
16
components/ui/table/Table.vue
Normal file
16
components/ui/table/Table.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="relative w-full overflow-auto">
|
||||||
|
<table :class="cn('w-full caption-bottom text-sm', props.class)">
|
||||||
|
<slot />
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
14
components/ui/table/TableBody.vue
Normal file
14
components/ui/table/TableBody.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<tbody :class="cn('[&_tr:last-child]:border-0', props.class)">
|
||||||
|
<slot />
|
||||||
|
</tbody>
|
||||||
|
</template>
|
||||||
14
components/ui/table/TableCaption.vue
Normal file
14
components/ui/table/TableCaption.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<caption :class="cn('mt-4 text-sm text-muted-foreground', props.class)">
|
||||||
|
<slot />
|
||||||
|
</caption>
|
||||||
|
</template>
|
||||||
21
components/ui/table/TableCell.vue
Normal file
21
components/ui/table/TableCell.vue
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<td
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'p-4 align-middle [&:has([role=checkbox])]:pr-0',
|
||||||
|
props.class,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</td>
|
||||||
|
</template>
|
||||||
38
components/ui/table/TableEmpty.vue
Normal file
38
components/ui/table/TableEmpty.vue
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { computed } from "vue"
|
||||||
|
import TableCell from "./TableCell.vue"
|
||||||
|
import TableRow from "./TableRow.vue"
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
colspan?: number
|
||||||
|
}>(), {
|
||||||
|
colspan: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'p-4 whitespace-nowrap align-middle text-sm text-foreground',
|
||||||
|
props.class,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
v-bind="delegatedProps"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-center py-10">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</template>
|
||||||
14
components/ui/table/TableFooter.vue
Normal file
14
components/ui/table/TableFooter.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<tfoot :class="cn('border-t bg-muted/50 font-medium last:[&>tr]:border-b-0', props.class)">
|
||||||
|
<slot />
|
||||||
|
</tfoot>
|
||||||
|
</template>
|
||||||
14
components/ui/table/TableHead.vue
Normal file
14
components/ui/table/TableHead.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<th :class="cn('h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0', props.class)">
|
||||||
|
<slot />
|
||||||
|
</th>
|
||||||
|
</template>
|
||||||
14
components/ui/table/TableHeader.vue
Normal file
14
components/ui/table/TableHeader.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<thead :class="cn('[&_tr]:border-b', props.class)">
|
||||||
|
<slot />
|
||||||
|
</thead>
|
||||||
|
</template>
|
||||||
14
components/ui/table/TableRow.vue
Normal file
14
components/ui/table/TableRow.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<tr :class="cn('border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted', props.class)">
|
||||||
|
<slot />
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
9
components/ui/table/index.ts
Normal file
9
components/ui/table/index.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export { default as Table } from './Table.vue'
|
||||||
|
export { default as TableBody } from './TableBody.vue'
|
||||||
|
export { default as TableCaption } from './TableCaption.vue'
|
||||||
|
export { default as TableCell } from './TableCell.vue'
|
||||||
|
export { default as TableEmpty } from './TableEmpty.vue'
|
||||||
|
export { default as TableFooter } from './TableFooter.vue'
|
||||||
|
export { default as TableHead } from './TableHead.vue'
|
||||||
|
export { default as TableHeader } from './TableHeader.vue'
|
||||||
|
export { default as TableRow } from './TableRow.vue'
|
||||||
@@ -16,7 +16,7 @@ const delegatedProps = computed(() => {
|
|||||||
<TabsContent
|
<TabsContent
|
||||||
:class="
|
:class="
|
||||||
cn(
|
cn(
|
||||||
'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
'mt-2 ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
||||||
props.class
|
props.class
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const forwardedProps = useForwardProps(delegatedProps)
|
|||||||
v-bind="forwardedProps"
|
v-bind="forwardedProps"
|
||||||
:class="
|
:class="
|
||||||
cn(
|
cn(
|
||||||
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm',
|
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-xs',
|
||||||
props.class
|
props.class
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -17,6 +17,6 @@ const forwardedProps = useForwardProps(delegatedProps)
|
|||||||
<template>
|
<template>
|
||||||
<TagsInputInput
|
<TagsInputInput
|
||||||
v-bind="forwardedProps"
|
v-bind="forwardedProps"
|
||||||
:class="cn('text-sm min-h-6 focus:outline-none flex-1 bg-transparent px-1', props.class)"
|
:class="cn('text-sm min-h-6 focus:outline-hidden flex-1 bg-transparent px-1', props.class)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const delegatedProps = computed(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ToastAction v-bind="delegatedProps" :class="cn('inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive', props.class)">
|
<ToastAction v-bind="delegatedProps" :class="cn('inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 hover:group-[.destructive]:border-destructive/30 hover:group-[.destructive]:bg-destructive hover:group-[.destructive]:text-destructive-foreground focus:group-[.destructive]:ring-destructive', props.class)">
|
||||||
<slot />
|
<slot />
|
||||||
</ToastAction>
|
</ToastAction>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const delegatedProps = computed(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ToastClose v-bind="delegatedProps" :class="cn('absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600', props.class)">
|
<ToastClose v-bind="delegatedProps" :class="cn('absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-hidden focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 hover:group-[.destructive]:text-red-50 focus:group-[.destructive]:ring-red-400 focus:group-[.destructive]:ring-offset-red-600', props.class)">
|
||||||
<X class="h-4 w-4" />
|
<X class="h-4 w-4" />
|
||||||
</ToastClose>
|
</ToastClose>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user