Added default layout to project
* Added default layout for the site * App is in light mode by default. * App is in light mode by default.
This commit is contained in:
parent
9a4dd4b721
commit
d1967f718e
36 changed files with 1660 additions and 39 deletions
22
.vscode/settings.json
vendored
Normal file
22
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
},
|
||||
"[vue]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact",
|
||||
"vue"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<div>
|
||||
<NuxtRouteAnnouncer />
|
||||
<div class="w-full min-h-screen flex justify-center items-top">
|
||||
<NuxtLayout>
|
||||
<NuxtPage />
|
||||
</div>
|
||||
</NuxtLayout>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
BIN
app/assets/images/logo.png
Normal file
BIN
app/assets/images/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
24
app/assets/scss/layout/_core.scss
Normal file
24
app/assets/scss/layout/_core.scss
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
html {
|
||||
height: 100%;
|
||||
font-size: 14px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Ubuntu, Arial, Helvetica, sans-serif;
|
||||
color: var(--text-color);
|
||||
background-color: var(--surface-ground);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 100%;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.layout-wrapper {
|
||||
min-height: 100vh;
|
||||
}
|
||||
8
app/assets/scss/layout/_footer.scss
Normal file
8
app/assets/scss/layout/_footer.scss
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
.layout-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1rem 0 1rem 0;
|
||||
gap: 0.5rem;
|
||||
border-top: 1px solid var(--surface-border);
|
||||
}
|
||||
14
app/assets/scss/layout/_main.scss
Normal file
14
app/assets/scss/layout/_main.scss
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
.layout-main-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
justify-content: space-between;
|
||||
padding: 6rem 2rem 0 2rem;
|
||||
background-color: var(--surface-ground);
|
||||
transition: margin-left var(--layout-section-transition-duration);
|
||||
}
|
||||
|
||||
.layout-main {
|
||||
flex: 1 1 auto;
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
160
app/assets/scss/layout/_menu.scss
Normal file
160
app/assets/scss/layout/_menu.scss
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
@use "mixins" as *;
|
||||
|
||||
.layout-sidebar {
|
||||
position: fixed;
|
||||
width: 20rem;
|
||||
height: calc(100vh - 8rem);
|
||||
z-index: 999;
|
||||
overflow-y: auto;
|
||||
user-select: none;
|
||||
top: 6rem;
|
||||
left: 2rem;
|
||||
transition:
|
||||
transform var(--layout-section-transition-duration),
|
||||
left var(--layout-section-transition-duration);
|
||||
background-color: var(--surface-overlay);
|
||||
border-radius: var(--content-border-radius);
|
||||
padding: 0.5rem 1.5rem;
|
||||
}
|
||||
|
||||
.layout-menu {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
|
||||
.layout-root-menuitem {
|
||||
> .layout-menuitem-root-text {
|
||||
font-size: 0.857rem;
|
||||
text-transform: uppercase;
|
||||
font-weight: 700;
|
||||
color: var(--text-color);
|
||||
margin: 0.75rem 0;
|
||||
}
|
||||
|
||||
> a {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
user-select: none;
|
||||
|
||||
&.active-menuitem {
|
||||
> .layout-submenu-toggler {
|
||||
transform: rotate(-180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
li.active-menuitem {
|
||||
> a {
|
||||
.layout-submenu-toggler {
|
||||
transform: rotate(-180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
outline: 0 none;
|
||||
color: var(--text-color);
|
||||
cursor: pointer;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: var(--content-border-radius);
|
||||
transition:
|
||||
background-color var(--element-transition-duration),
|
||||
box-shadow var(--element-transition-duration);
|
||||
|
||||
.layout-menuitem-icon {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.layout-submenu-toggler {
|
||||
font-size: 75%;
|
||||
margin-left: auto;
|
||||
transition: transform var(--element-transition-duration);
|
||||
}
|
||||
|
||||
&.active-route {
|
||||
font-weight: 700;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--surface-hover);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
@include focused-inset();
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
overflow: hidden;
|
||||
border-radius: var(--content-border-radius);
|
||||
|
||||
li {
|
||||
a {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
li {
|
||||
a {
|
||||
margin-left: 2rem;
|
||||
}
|
||||
|
||||
li {
|
||||
a {
|
||||
margin-left: 2.5rem;
|
||||
}
|
||||
|
||||
li {
|
||||
a {
|
||||
margin-left: 3rem;
|
||||
}
|
||||
|
||||
li {
|
||||
a {
|
||||
margin-left: 3.5rem;
|
||||
}
|
||||
|
||||
li {
|
||||
a {
|
||||
margin-left: 4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout-submenu-enter-from,
|
||||
.layout-submenu-leave-to {
|
||||
max-height: 0;
|
||||
}
|
||||
|
||||
.layout-submenu-enter-to,
|
||||
.layout-submenu-leave-from {
|
||||
max-height: 1000px;
|
||||
}
|
||||
|
||||
.layout-submenu-leave-active {
|
||||
overflow: hidden;
|
||||
transition: max-height 0.45s cubic-bezier(0, 1, 0, 1);
|
||||
}
|
||||
|
||||
.layout-submenu-enter-active {
|
||||
overflow: hidden;
|
||||
transition: max-height 1s ease-in-out;
|
||||
}
|
||||
15
app/assets/scss/layout/_mixins.scss
Normal file
15
app/assets/scss/layout/_mixins.scss
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
@mixin focused() {
|
||||
outline-width: var(--focus-ring-width);
|
||||
outline-style: var(--focus-ring-style);
|
||||
outline-color: var(--focus-ring-color);
|
||||
outline-offset: var(--focus-ring-offset);
|
||||
box-shadow: var(--focus-ring-shadow);
|
||||
transition:
|
||||
box-shadow var(--transition-duration),
|
||||
outline-color var(--transition-duration);
|
||||
}
|
||||
|
||||
@mixin focused-inset() {
|
||||
outline-offset: -1px;
|
||||
box-shadow: inset var(--focus-ring-shadow);
|
||||
}
|
||||
48
app/assets/scss/layout/_preloading.scss
Normal file
48
app/assets/scss/layout/_preloading.scss
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
.preloader {
|
||||
position: fixed;
|
||||
z-index: 999999;
|
||||
background: #edf1f5;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.preloader-content {
|
||||
border: 0 solid transparent;
|
||||
border-radius: 50%;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
position: absolute;
|
||||
top: calc(50vh - 75px);
|
||||
left: calc(50vw - 75px);
|
||||
}
|
||||
|
||||
.preloader-content:before,
|
||||
.preloader-content:after {
|
||||
content: "";
|
||||
border: 1em solid var(--primary-color);
|
||||
border-radius: 50%;
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
animation: loader 2s linear infinite;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.preloader-content:before {
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
|
||||
@keyframes loader {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
110
app/assets/scss/layout/_responsive.scss
Normal file
110
app/assets/scss/layout/_responsive.scss
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
@media screen and (min-width: 1960px) {
|
||||
.layout-main,
|
||||
.landing-wrapper {
|
||||
width: 1504px;
|
||||
margin-left: auto !important;
|
||||
margin-right: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.layout-wrapper {
|
||||
&.layout-overlay {
|
||||
.layout-main-container {
|
||||
margin-left: 0;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
.layout-sidebar {
|
||||
transform: translateX(-100%);
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-right: 1px solid var(--surface-border);
|
||||
transition:
|
||||
transform 0.4s cubic-bezier(0.05, 0.74, 0.2, 0.99),
|
||||
left 0.4s cubic-bezier(0.05, 0.74, 0.2, 0.99);
|
||||
box-shadow:
|
||||
0px 3px 5px rgba(0, 0, 0, 0.02),
|
||||
0px 0px 2px rgba(0, 0, 0, 0.05),
|
||||
0px 1px 4px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
&.layout-overlay-active {
|
||||
.layout-sidebar {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.layout-static {
|
||||
.layout-main-container {
|
||||
margin-left: 22rem;
|
||||
}
|
||||
|
||||
&.layout-static-inactive {
|
||||
.layout-sidebar {
|
||||
transform: translateX(-100%);
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.layout-main-container {
|
||||
margin-left: 0;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout-mask {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.blocked-scroll {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.layout-wrapper {
|
||||
.layout-main-container {
|
||||
margin-left: 0;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
.layout-sidebar {
|
||||
transform: translateX(-100%);
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
transition:
|
||||
transform 0.4s cubic-bezier(0.05, 0.74, 0.2, 0.99),
|
||||
left 0.4s cubic-bezier(0.05, 0.74, 0.2, 0.99);
|
||||
}
|
||||
|
||||
.layout-mask {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 998;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--maskbg);
|
||||
}
|
||||
|
||||
&.layout-mobile-active {
|
||||
.layout-sidebar {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.layout-mask {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
201
app/assets/scss/layout/_topbar.scss
Normal file
201
app/assets/scss/layout/_topbar.scss
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
@use "mixins" as *;
|
||||
|
||||
.layout-topbar {
|
||||
position: fixed;
|
||||
height: 4rem;
|
||||
z-index: 997;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
padding: 0 2rem;
|
||||
background-color: var(--surface-card);
|
||||
transition: left var(--layout-section-transition-duration);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.layout-topbar-logo-container {
|
||||
width: 20rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.layout-topbar-logo {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-size: 1.5rem;
|
||||
border-radius: var(--content-border-radius);
|
||||
color: var(--text-color);
|
||||
font-weight: 500;
|
||||
gap: 0.5rem;
|
||||
|
||||
img {
|
||||
width: 3rem;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
@include focused();
|
||||
}
|
||||
}
|
||||
|
||||
.layout-topbar-action {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 50%;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
color: var(--text-color);
|
||||
transition: background-color var(--element-transition-duration);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--surface-hover);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
@include focused();
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 1rem;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.layout-topbar-action-highlight {
|
||||
background-color: var(--primary-color);
|
||||
color: var(--primary-contrast-color);
|
||||
}
|
||||
}
|
||||
|
||||
.layout-menu-button {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.layout-topbar-menu-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.layout-topbar-actions {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.layout-topbar-menu-content {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.layout-config-menu {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.layout-topbar {
|
||||
padding: 0 2rem;
|
||||
|
||||
.layout-topbar-logo-container {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.layout-menu-button {
|
||||
margin-left: 0;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.layout-topbar-menu-button {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.layout-topbar-menu {
|
||||
position: absolute;
|
||||
background-color: var(--surface-overlay);
|
||||
transform-origin: top;
|
||||
box-shadow:
|
||||
0px 3px 5px rgba(0, 0, 0, 0.02),
|
||||
0px 0px 2px rgba(0, 0, 0, 0.05),
|
||||
0px 1px 4px rgba(0, 0, 0, 0.08);
|
||||
border-radius: var(--content-border-radius);
|
||||
padding: 1rem;
|
||||
right: 2rem;
|
||||
top: 4rem;
|
||||
min-width: 15rem;
|
||||
border: 1px solid var(--surface-border);
|
||||
|
||||
.layout-topbar-menu-content {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.layout-topbar-action {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
justify-content: flex-start;
|
||||
border-radius: var(--content-border-radius);
|
||||
padding: 0.5rem 1rem;
|
||||
|
||||
i {
|
||||
font-size: 1rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
span {
|
||||
font-weight: medium;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout-topbar-menu-content {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.config-panel {
|
||||
.config-panel-label {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-secondary-color);
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.config-panel-colors {
|
||||
> div {
|
||||
padding-top: 0.5rem;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
|
||||
button {
|
||||
border: none;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
border-radius: 50%;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
outline-color: transparent;
|
||||
outline-width: 2px;
|
||||
outline-style: solid;
|
||||
outline-offset: 1px;
|
||||
|
||||
&.active-color {
|
||||
outline-color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.config-panel-settings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
68
app/assets/scss/layout/_typography.scss
Normal file
68
app/assets/scss/layout/_typography.scss
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin: 1.5rem 0 1rem 0;
|
||||
font-family: inherit;
|
||||
font-weight: 700;
|
||||
line-height: 1.5;
|
||||
color: var(--text-color);
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
mark {
|
||||
background: #fff8e1;
|
||||
padding: 0.25rem 0.4rem;
|
||||
border-radius: var(--content-border-radius);
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 1rem 0;
|
||||
padding: 0 2rem;
|
||||
border-left: 4px solid #90a4ae;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-top: solid var(--surface-border);
|
||||
border-width: 1px 0 0 0;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 1rem 0;
|
||||
line-height: 1.5;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
25
app/assets/scss/layout/_utils.scss
Normal file
25
app/assets/scss/layout/_utils.scss
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/* Utils */
|
||||
.clearfix:after {
|
||||
content: " ";
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--surface-card);
|
||||
padding: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
border-radius: var(--content-border-radius);
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.p-toast {
|
||||
&.p-toast-top-right,
|
||||
&.p-toast-top-left,
|
||||
&.p-toast-top-center {
|
||||
top: 100px;
|
||||
}
|
||||
}
|
||||
11
app/assets/scss/layout/layout.scss
Normal file
11
app/assets/scss/layout/layout.scss
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
@use "./variables/_common";
|
||||
@use "./_mixins";
|
||||
@use "./_preloading";
|
||||
@use "./_core";
|
||||
@use "./_main";
|
||||
@use "./_topbar";
|
||||
@use "./_menu";
|
||||
@use "./_footer";
|
||||
@use "./_responsive";
|
||||
@use "./_utils";
|
||||
@use "./_typography";
|
||||
26
app/assets/scss/layout/variables/_common.scss
Normal file
26
app/assets/scss/layout/variables/_common.scss
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
:root {
|
||||
--primary-color: var(--p-primary-color);
|
||||
--primary-contrast-color: var(--p-primary-contrast-color);
|
||||
--text-color: var(--p-text-color);
|
||||
--text-color-secondary: var(--p-text-muted-color);
|
||||
--surface-ground: var(--p-surface-100);
|
||||
--surface-border: var(--p-content-border-color);
|
||||
--surface-card: var(--p-content-background);
|
||||
--surface-hover: var(--p-content-hover-background);
|
||||
--surface-overlay: var(--p-overlay-popover-background);
|
||||
--transition-duration: var(--p-transition-duration);
|
||||
--maskbg: var(--p-mask-background);
|
||||
--content-border-radius: var(--p-content-border-radius);
|
||||
--layout-section-transition-duration: 0.2s;
|
||||
--element-transition-duration: var(--p-transition-duration);
|
||||
--focus-ring-width: var(--p-focus-ring-width);
|
||||
--focus-ring-style: var(--p-focus-ring-style);
|
||||
--focus-ring-color: var(--p-focus-ring-color);
|
||||
--focus-ring-offset: var(--p-focus-ring-offset);
|
||||
--focus-ring-shadow: var(--p-focus-ring-shadow);
|
||||
}
|
||||
|
||||
:root[class="app-dark"] {
|
||||
--p-text-color: #ffffff;
|
||||
--surface-ground: var(--p-surface-950);
|
||||
}
|
||||
3
app/assets/scss/styles.scss
Normal file
3
app/assets/scss/styles.scss
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
@use "primeicons/primeicons.css";
|
||||
@use "~/assets/css/tailwind.css";
|
||||
@use "~/assets/scss/layout/layout.scss";
|
||||
21
app/layouts/Default.vue
Normal file
21
app/layouts/Default.vue
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<template>
|
||||
<div class="layout-wrapper layout-static">
|
||||
<default-topbar />
|
||||
<default-sidebar />
|
||||
<div class="layout-main-container">
|
||||
<div class="layout-main">
|
||||
<slot />
|
||||
</div>
|
||||
<default-footer />
|
||||
</div>
|
||||
<div class="layout-mask animate-fadein"></div>
|
||||
</div>
|
||||
<Toast />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Toast from "primevue/toast";
|
||||
import DefaultTopbar from "~/layouts/default/Topbar.vue";
|
||||
import DefaultSidebar from "~/layouts/default/Sidebar.vue";
|
||||
import DefaultFooter from "~/layouts/default/Footer.vue";
|
||||
</script>
|
||||
9
app/layouts/default/Footer.vue
Normal file
9
app/layouts/default/Footer.vue
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
const currentYear = new Date().getFullYear();
|
||||
</script>
|
||||
<template>
|
||||
<footer class="layout-footer font-bold">
|
||||
<div v-if="currentYear === 2025">Glowing Fiesta 2025</div>
|
||||
<div v-else>Glowing Fiesta 2025 - {{ currentYear }}</div>
|
||||
</footer>
|
||||
</template>
|
||||
35
app/layouts/default/Sidebar.vue
Normal file
35
app/layouts/default/Sidebar.vue
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<template>
|
||||
<div class="layout-sidebar">
|
||||
<ul class="layout-menu">
|
||||
<li class="layout-root-menuitem">
|
||||
<div class="layout-menuitem-root-text">Home</div>
|
||||
<ul class="layout-submenu">
|
||||
<li>
|
||||
<nuxt-link to="/">
|
||||
<i class="pi pi-home layout-menuitem-icon"></i>
|
||||
<span class="layout-menuitem-text">Dashboard</span>
|
||||
</nuxt-link>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li class="layout-root-menuitem">
|
||||
<div class="layout-menuitem-root-text">Configuration</div>
|
||||
<ul class="layout-submenu">
|
||||
<li>
|
||||
<nuxt-link to="/config/account">
|
||||
<i class="pi pi-user layout-menuitem-icon"></i>
|
||||
<span class="layout-menuitem-text">Account</span>
|
||||
</nuxt-link>
|
||||
</li>
|
||||
<li>
|
||||
<nuxt-link to="/config/settings">
|
||||
<i class="pi pi-cog layout-menuitem-icon"></i>
|
||||
<span class="layout-menuitem-text">Settings</span>
|
||||
</nuxt-link>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
56
app/layouts/default/Topbar.vue
Normal file
56
app/layouts/default/Topbar.vue
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
|
||||
const isDarkTheme = ref(false);
|
||||
|
||||
const toggleDarkMode = () => {
|
||||
isDarkTheme.value = !isDarkTheme.value;
|
||||
document.documentElement.classList.toggle("app-dark");
|
||||
};
|
||||
|
||||
const toggleMenu = () => {
|
||||
document.getElementsByClassName("layout-wrapper")[0]?.classList.toggle("layout-static-inactive");
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="layout-topbar">
|
||||
<div class="layout-topbar-logo-container">
|
||||
<button class="layout-menu-button layout-topbar-action" @click="toggleMenu">
|
||||
<i class="pi pi-bars"></i>
|
||||
</button>
|
||||
|
||||
<nuxt-link to="/" class="layout-topbar-logo">
|
||||
<img src="@/assets/images/logo.png" alt="Two stick silhouettes admiring fireworks in the sky" />
|
||||
<span>Glowing Fiesta</span>
|
||||
</nuxt-link>
|
||||
</div>
|
||||
|
||||
<div class="layout-topbar-actions">
|
||||
<div class="layout-config-menu">
|
||||
<button type="button" class="layout-topbar-action layout-topbar-action-highlight" @click="toggleDarkMode">
|
||||
<i :class="['pi', { 'pi-moon': isDarkTheme, 'pi-sun': !isDarkTheme }]"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Divider layout="vertical" />
|
||||
|
||||
<div class="layout-topbar-menu hidden lg:block">
|
||||
<div class="layout-topbar-menu-content">
|
||||
<button type="button" class="layout-topbar-action">
|
||||
<i class="pi pi-calendar"></i>
|
||||
<span>Calendar</span>
|
||||
</button>
|
||||
<button type="button" class="layout-topbar-action">
|
||||
<i class="pi pi-inbox"></i>
|
||||
<span>Messages</span>
|
||||
</button>
|
||||
<button type="button" class="layout-topbar-action">
|
||||
<i class="pi pi-user"></i>
|
||||
<span>Profile</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
5
app/pages/config/Account.vue
Normal file
5
app/pages/config/Account.vue
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<div class="card">
|
||||
<h1>Account</h1>
|
||||
</div>
|
||||
</template>
|
||||
5
app/pages/config/Settings.vue
Normal file
5
app/pages/config/Settings.vue
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<div class="card">
|
||||
<h1>Settings</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,21 +1,5 @@
|
|||
<template>
|
||||
<article class="w-3/4 flex flex-col gap-4 m-8 text-center">
|
||||
<header class="mb-4">
|
||||
<h1 class="w-full text-4xl font-bold">Homepage</h1>
|
||||
<p class="w-full">This is your homepage</p>
|
||||
</header>
|
||||
|
||||
<div class="article-content w-full">
|
||||
<h2 class="w-full text-xl font-bold">First paragraph</h2>
|
||||
<p class="w-full text-justify mb-4">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam eu ornare velit. Curabitur urna felis, malesuada et sapien
|
||||
eu, tincidunt consectetur sem. Cras eu tortor nec lorem vehicula sollicitudin. Sed et pellentesque dolor, et faucibus
|
||||
magna nulla.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<Button label="Verify PrimeVue" />
|
||||
</footer>
|
||||
</article>
|
||||
<div class="card">
|
||||
<h1>Dashboard</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"
|
|||
export default withNuxt(eslintPluginPrettierRecommended, {
|
||||
files: ["**/*.{ts,js,vue}"],
|
||||
rules: {
|
||||
// Vue rules
|
||||
"vue/no-multiple-template-root": "off",
|
||||
|
||||
// Typescript rules
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-require-imports": "off",
|
||||
"@typescript-eslint/no-unused-vars": "warn",
|
||||
|
|
|
|||
|
|
@ -1,17 +1,77 @@
|
|||
import { definePreset } from "@primeuix/themes";
|
||||
import Aura from "@primeuix/themes/aura";
|
||||
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
export default defineNuxtConfig({
|
||||
compatibilityDate: "2025-07-15",
|
||||
devtools: { enabled: true },
|
||||
modules: ["@nuxt/eslint", "@nuxtjs/tailwindcss", "@primevue/nuxt-module"],
|
||||
primevue: {
|
||||
options: {
|
||||
ripple: true,
|
||||
theme: {
|
||||
preset: Aura,
|
||||
options: {
|
||||
darkModeSelector: "system",
|
||||
const defaultPreset = definePreset(Aura, {
|
||||
semantic: {
|
||||
primary: {
|
||||
50: "{teal.50}",
|
||||
100: "{teal.100}",
|
||||
200: "{teal.200}",
|
||||
300: "{teal.300}",
|
||||
400: "{teal.400}",
|
||||
500: "{teal.500}",
|
||||
600: "{teal.600}",
|
||||
700: "{teal.700}",
|
||||
800: "{teal.800}",
|
||||
900: "{teal.900}",
|
||||
950: "{teal.950}",
|
||||
},
|
||||
colorScheme: {
|
||||
light: {
|
||||
surface: {
|
||||
0: "#ffffff",
|
||||
50: "{slate.50}",
|
||||
100: "{slate.100}",
|
||||
200: "{slate.200}",
|
||||
300: "{slate.300}",
|
||||
400: "{slate.400}",
|
||||
500: "{slate.500}",
|
||||
600: "{slate.600}",
|
||||
700: "{slate.700}",
|
||||
800: "{slate.800}",
|
||||
900: "{slate.900}",
|
||||
950: "{slate.950}",
|
||||
},
|
||||
},
|
||||
dark: {
|
||||
surface: {
|
||||
0: "#000000",
|
||||
50: "{slate.50}",
|
||||
100: "{slate.100}",
|
||||
200: "{slate.200}",
|
||||
300: "{slate.300}",
|
||||
400: "{slate.400}",
|
||||
500: "{slate.500}",
|
||||
600: "{slate.600}",
|
||||
700: "{slate.700}",
|
||||
800: "{slate.800}",
|
||||
900: "{slate.900}",
|
||||
950: "{slate.950}",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
export default defineNuxtConfig({
|
||||
compatibilityDate: "2025-07-15",
|
||||
devtools: { enabled: true },
|
||||
router: {
|
||||
options: {
|
||||
linkActiveClass: "active-route",
|
||||
linkExactActiveClass: "exact-active-route",
|
||||
},
|
||||
},
|
||||
modules: ["@nuxt/eslint", "@nuxtjs/tailwindcss", "@primevue/nuxt-module"],
|
||||
css: ["~/assets/css/tailwind.css", "~/assets/scss/styles.scss"],
|
||||
primevue: {
|
||||
options: {
|
||||
ripple: true,
|
||||
theme: {
|
||||
preset: defaultPreset,
|
||||
options: {
|
||||
darkModeSelector: ".app-dark",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
448
package-lock.json
generated
448
package-lock.json
generated
|
|
@ -10,6 +10,7 @@
|
|||
"@nuxtjs/tailwindcss": "^7.0.0-beta.1",
|
||||
"@primeuix/themes": "^1.2.5",
|
||||
"nuxt": "^4.1.3",
|
||||
"primeicons": "^7.0.0",
|
||||
"primevue": "^4.4.1",
|
||||
"tailwindcss": "^4.1.16",
|
||||
"vue": "^3.5.22",
|
||||
|
|
@ -26,6 +27,7 @@
|
|||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"happy-dom": "^20.0.8",
|
||||
"prettier": "^3.6.2",
|
||||
"sass-embedded": "^1.93.3",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.1"
|
||||
}
|
||||
|
|
@ -482,6 +484,13 @@
|
|||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/protobuf": {
|
||||
"version": "2.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.10.0.tgz",
|
||||
"integrity": "sha512-fdRs9PSrBF7QUntpZpq6BTw58fhgGJojgg39m9oFOJGZT+nip9b0so5cYY1oWl5pvemDLr0cPPsH46vwThEbpQ==",
|
||||
"devOptional": true,
|
||||
"license": "(Apache-2.0 AND BSD-3-Clause)"
|
||||
},
|
||||
"node_modules/@clack/core": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@clack/core/-/core-0.5.0.tgz",
|
||||
|
|
@ -5839,6 +5848,13 @@
|
|||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-builder": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz",
|
||||
"integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT/X11"
|
||||
},
|
||||
"node_modules/buffer-crc32": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz",
|
||||
|
|
@ -6365,6 +6381,13 @@
|
|||
"integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/colorjs.io": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz",
|
||||
"integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
|
||||
|
|
@ -8619,6 +8642,13 @@
|
|||
"integrity": "sha512-3MOLanc3sb3LNGWQl1RlQlNWURE5g32aUphrDyFeCsxBTk08iE3VNe4CwsUZ0Qs1X+EfX0+r29Sxdpza4B+yRA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/immutable": {
|
||||
"version": "5.1.4",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz",
|
||||
"integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/import-fresh": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
||||
|
|
@ -11403,6 +11433,12 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/primeicons": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/primeicons/-/primeicons-7.0.0.tgz",
|
||||
"integrity": "sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/primevue": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/primevue/-/primevue-4.4.1.tgz",
|
||||
|
|
@ -11843,6 +11879,16 @@
|
|||
"queue-microtask": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/rxjs": {
|
||||
"version": "7.8.2",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
||||
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
|
|
@ -11863,6 +11909,374 @@
|
|||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.93.3",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.93.3.tgz",
|
||||
"integrity": "sha512-elOcIZRTM76dvxNAjqYrucTSI0teAF/L2Lv0s6f6b7FOwcwIuA357bIE871580AjHJuSvLIRUosgV+lIWx6Rgg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.0",
|
||||
"immutable": "^5.0.2",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"sass": "sass.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@parcel/watcher": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded": {
|
||||
"version": "1.93.3",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.93.3.tgz",
|
||||
"integrity": "sha512-+VUy01yfDqNmIVMd/LLKl2TTtY0ovZN0rTonh+FhKr65mFwIYgU9WzgIZKS7U9/SPCQvWTsTGx9jyt+qRm/XFw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@bufbuild/protobuf": "^2.5.0",
|
||||
"buffer-builder": "^0.2.0",
|
||||
"colorjs.io": "^0.5.0",
|
||||
"immutable": "^5.0.2",
|
||||
"rxjs": "^7.4.0",
|
||||
"supports-color": "^8.1.1",
|
||||
"sync-child-process": "^1.0.2",
|
||||
"varint": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"sass": "dist/bin/sass.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"sass-embedded-all-unknown": "1.93.3",
|
||||
"sass-embedded-android-arm": "1.93.3",
|
||||
"sass-embedded-android-arm64": "1.93.3",
|
||||
"sass-embedded-android-riscv64": "1.93.3",
|
||||
"sass-embedded-android-x64": "1.93.3",
|
||||
"sass-embedded-darwin-arm64": "1.93.3",
|
||||
"sass-embedded-darwin-x64": "1.93.3",
|
||||
"sass-embedded-linux-arm": "1.93.3",
|
||||
"sass-embedded-linux-arm64": "1.93.3",
|
||||
"sass-embedded-linux-musl-arm": "1.93.3",
|
||||
"sass-embedded-linux-musl-arm64": "1.93.3",
|
||||
"sass-embedded-linux-musl-riscv64": "1.93.3",
|
||||
"sass-embedded-linux-musl-x64": "1.93.3",
|
||||
"sass-embedded-linux-riscv64": "1.93.3",
|
||||
"sass-embedded-linux-x64": "1.93.3",
|
||||
"sass-embedded-unknown-all": "1.93.3",
|
||||
"sass-embedded-win32-arm64": "1.93.3",
|
||||
"sass-embedded-win32-x64": "1.93.3"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-all-unknown": {
|
||||
"version": "1.93.3",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.93.3.tgz",
|
||||
"integrity": "sha512-3okGgnE41eg+CPLtAPletu6nQ4N0ij7AeW+Sl5Km4j29XcmqZQeFwYjHe1AlKTEgLi/UAONk1O8i8/lupeKMbw==",
|
||||
"cpu": [
|
||||
"!arm",
|
||||
"!arm64",
|
||||
"!riscv64",
|
||||
"!x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"sass": "1.93.3"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-android-arm": {
|
||||
"version": "1.93.3",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.93.3.tgz",
|
||||
"integrity": "sha512-8xOw9bywfOD6Wv24BgCmgjkk6tMrsOTTHcb28KDxeJtFtoxiUyMbxo0vChpPAfp2Hyg2tFFKS60s0s4JYk+Raw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-android-arm64": {
|
||||
"version": "1.93.3",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.93.3.tgz",
|
||||
"integrity": "sha512-uqUl3Kt1IqdGVAcAdbmC+NwuUJy8tM+2ZnB7/zrt6WxWVShVCRdFnWR9LT8HJr7eJN7AU8kSXxaVX/gedanPsg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-android-riscv64": {
|
||||
"version": "1.93.3",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.93.3.tgz",
|
||||
"integrity": "sha512-2jNJDmo+3qLocjWqYbXiBDnfgwrUeZgZFHJIwAefU7Fn66Ot7rsXl+XPwlokaCbTpj7eMFIqsRAZ/uDueXNCJg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-android-x64": {
|
||||
"version": "1.93.3",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.93.3.tgz",
|
||||
"integrity": "sha512-y0RoAU6ZenQFcjM9PjQd3cRqRTjqwSbtWLL/p68y2oFyh0QGN0+LQ826fc0ZvU/AbqCsAizkqjzOn6cRZJxTTQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-darwin-arm64": {
|
||||
"version": "1.93.3",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.93.3.tgz",
|
||||
"integrity": "sha512-7zb/hpdMOdKteK17BOyyypemglVURd1Hdz6QGsggy60aUFfptTLQftLRg8r/xh1RbQAUKWFbYTNaM47J9yPxYg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-darwin-x64": {
|
||||
"version": "1.93.3",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.93.3.tgz",
|
||||
"integrity": "sha512-Ek1Vp8ZDQEe327Lz0b7h3hjvWH3u9XjJiQzveq74RPpJQ2q6d9LfWpjiRRohM4qK6o4XOHw1X10OMWPXJtdtWg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-linux-arm": {
|
||||
"version": "1.93.3",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.93.3.tgz",
|
||||
"integrity": "sha512-yeiv2y+dp8B4wNpd3+JsHYD0mvpXSfov7IGyQ1tMIR40qv+ROkRqYiqQvAOXf76Qwh4Y9OaYZtLpnsPjfeq6mA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-linux-arm64": {
|
||||
"version": "1.93.3",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.93.3.tgz",
|
||||
"integrity": "sha512-RBrHWgfd8Dd8w4fbmdRVXRrhh8oBAPyeWDTKAWw8ZEmuXfVl4ytjDuyxaVilh6rR1xTRTNpbaA/YWApBlLrrNw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-linux-musl-arm": {
|
||||
"version": "1.93.3",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.93.3.tgz",
|
||||
"integrity": "sha512-fU0fwAwbp7sBE3h5DVU5UPzvaLg7a4yONfFWkkcCp6ZrOiPuGRHXXYriWQ0TUnWy4wE+svsVuWhwWgvlb/tkKg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-linux-musl-arm64": {
|
||||
"version": "1.93.3",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.93.3.tgz",
|
||||
"integrity": "sha512-PS829l+eUng+9W4PFclXGb4uA2+965NHV3/Sa5U7qTywjeeUUYTZg70dJHSqvhrBEfCc2XJABeW3adLJbyQYkw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-linux-musl-riscv64": {
|
||||
"version": "1.93.3",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.93.3.tgz",
|
||||
"integrity": "sha512-cK1oBY+FWQquaIGEeQ5H74KTO8cWsSWwXb/WaildOO9U6wmUypTgUYKQ0o5o/29nZbWWlM1PHuwVYTSnT23Jjg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-linux-musl-x64": {
|
||||
"version": "1.93.3",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.93.3.tgz",
|
||||
"integrity": "sha512-A7wkrsHu2/I4Zpa0NMuPGkWDVV7QGGytxGyUq3opSXgAexHo/vBPlGoDXoRlSdex0cV+aTMRPjoGIfdmNlHwyg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-linux-riscv64": {
|
||||
"version": "1.93.3",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.93.3.tgz",
|
||||
"integrity": "sha512-vWkW1+HTF5qcaHa6hO80gx/QfB6GGjJUP0xLbnAoY4pwEnw5ulGv6RM8qYr8IDhWfVt/KH+lhJ2ZFxnJareisQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-linux-x64": {
|
||||
"version": "1.93.3",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.93.3.tgz",
|
||||
"integrity": "sha512-k6uFxs+e5jSuk1Y0niCwuq42F9ZC5UEP7P+RIOurIm8w/5QFa0+YqeW+BPWEW5M1FqVOsNZH3qGn4ahqvAEjPA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-unknown-all": {
|
||||
"version": "1.93.3",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.93.3.tgz",
|
||||
"integrity": "sha512-o5wj2rLpXH0C+GJKt/VpWp6AnMsCCbfFmnMAttcrsa+U3yrs/guhZ3x55KAqqUsE8F47e3frbsDL+1OuQM5DAA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"!android",
|
||||
"!darwin",
|
||||
"!linux",
|
||||
"!win32"
|
||||
],
|
||||
"dependencies": {
|
||||
"sass": "1.93.3"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-win32-arm64": {
|
||||
"version": "1.93.3",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.93.3.tgz",
|
||||
"integrity": "sha512-0dOfT9moy9YmBolodwYYXtLwNr4jL4HQC9rBfv6mVrD7ud8ue2kDbn+GVzj1hEJxvEexVSmDCf7MHUTLcGs9xQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-win32-x64": {
|
||||
"version": "1.93.3",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.93.3.tgz",
|
||||
"integrity": "sha512-wHFVfxiS9hU/sNk7KReD+lJWRp3R0SLQEX4zfOnRP2zlvI2X4IQR5aZr9GNcuMP6TmNpX0nQPZTegS8+h9RrEg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded/node_modules/supports-color": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
||||
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
|
||||
|
|
@ -12444,6 +12858,29 @@
|
|||
"url": "https://opencollective.com/svgo"
|
||||
}
|
||||
},
|
||||
"node_modules/sync-child-process": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz",
|
||||
"integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sync-message-port": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sync-message-port": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.1.3.tgz",
|
||||
"integrity": "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/synckit": {
|
||||
"version": "0.11.11",
|
||||
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz",
|
||||
|
|
@ -12670,8 +13107,8 @@
|
|||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD",
|
||||
"optional": true
|
||||
"devOptional": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
|
|
@ -13148,6 +13585,13 @@
|
|||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/varint": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz",
|
||||
"integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "7.1.11",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.11.tgz",
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
"@nuxtjs/tailwindcss": "^7.0.0-beta.1",
|
||||
"@primeuix/themes": "^1.2.5",
|
||||
"nuxt": "^4.1.3",
|
||||
"primeicons": "^7.0.0",
|
||||
"primevue": "^4.4.1",
|
||||
"tailwindcss": "^4.1.16",
|
||||
"vue": "^3.5.22",
|
||||
|
|
@ -34,6 +35,7 @@
|
|||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"happy-dom": "^20.0.8",
|
||||
"prettier": "^3.6.2",
|
||||
"sass-embedded": "^1.93.3",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.1"
|
||||
}
|
||||
|
|
|
|||
11
tests/layouts/Default.test.ts
Normal file
11
tests/layouts/Default.test.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { mount, type VueWrapper } from "@vue/test-utils";
|
||||
import DefaultLayout from "~/layouts/Default.vue";
|
||||
|
||||
describe("Default.vue", () => {
|
||||
const wrapper: VueWrapper = mount(DefaultLayout, {});
|
||||
|
||||
it("loads without crashing", () => {
|
||||
expect(wrapper.exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
55
tests/layouts/default/Footer.test.ts
Normal file
55
tests/layouts/default/Footer.test.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { mount, type VueWrapper } from "@vue/test-utils";
|
||||
import Footer from "~/layouts/default/Footer.vue";
|
||||
|
||||
describe("Footer.vue", () => {
|
||||
const RealDate = Date;
|
||||
|
||||
const mockDate = (year: number) => {
|
||||
globalThis.Date = class extends RealDate {
|
||||
constructor() {
|
||||
super();
|
||||
return new RealDate(`${year}-01-01`);
|
||||
}
|
||||
|
||||
static now() {
|
||||
return new RealDate(`${year}-01-01`).getTime();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.Date = RealDate;
|
||||
});
|
||||
|
||||
it("loads without crashing", () => {
|
||||
const wrapper: VueWrapper = mount(Footer, {});
|
||||
expect(wrapper.exists()).toBe(true);
|
||||
expect(wrapper.find(".layout-footer").exists()).toBe(true);
|
||||
expect(wrapper.find(".layout-footer").classes()).toContain("font-bold");
|
||||
});
|
||||
|
||||
it("displays only 'Glowing Fiesta 2025' when current year is 2025", () => {
|
||||
mockDate(2025);
|
||||
|
||||
const wrapper = mount(Footer);
|
||||
expect(wrapper.text()).toBe("Glowing Fiesta 2025");
|
||||
expect(wrapper.text()).not.toContain(" - ");
|
||||
});
|
||||
|
||||
it("displays 'Glowing Fiesta 2025 - 2034' when current year is 2034", () => {
|
||||
mockDate(2034);
|
||||
|
||||
const wrapper = mount(Footer);
|
||||
expect(wrapper.text()).toBe("Glowing Fiesta 2025 - 2034");
|
||||
});
|
||||
|
||||
it("has proper structure / content", () => {
|
||||
const wrapper = mount(Footer);
|
||||
const footer = wrapper.find("footer");
|
||||
|
||||
expect(footer.exists()).toBe(true);
|
||||
expect(footer.element.tagName).toBe("FOOTER");
|
||||
expect(wrapper.findAll("div")).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
11
tests/layouts/default/Sidebar.test.ts
Normal file
11
tests/layouts/default/Sidebar.test.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { mount, type VueWrapper } from "@vue/test-utils";
|
||||
import Sidebar from "~/layouts/default/Sidebar.vue";
|
||||
|
||||
describe("Sidebar.vue", () => {
|
||||
it("loads without crashing", () => {
|
||||
const wrapper: VueWrapper = mount(Sidebar, {});
|
||||
expect(wrapper.exists()).toBe(true);
|
||||
expect(wrapper.find(".layout-sidebar").exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
155
tests/layouts/default/Topbar.test.ts
Normal file
155
tests/layouts/default/Topbar.test.ts
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { mount, type VueWrapper } from "@vue/test-utils";
|
||||
import Topbar from "~/layouts/default/Topbar.vue";
|
||||
|
||||
describe("Topbar.vue", () => {
|
||||
let wrapper: VueWrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
document.documentElement.className = "";
|
||||
document.body.innerHTML = '<div class="layout-wrapper"></div>';
|
||||
wrapper = mount(Topbar);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (wrapper) {
|
||||
wrapper.unmount();
|
||||
}
|
||||
});
|
||||
|
||||
describe("Component Rendering", () => {
|
||||
it("loads without crashing", () => {
|
||||
expect(wrapper.exists()).toBe(true);
|
||||
expect(wrapper.find(".layout-topbar").exists()).toBe(true);
|
||||
});
|
||||
|
||||
it("renders the logo image and text", () => {
|
||||
const logo = wrapper.find(".layout-topbar-logo");
|
||||
expect(logo.exists()).toBe(true);
|
||||
expect(logo.find("img").attributes("alt")).toBe("Two stick silhouettes admiring fireworks in the sky");
|
||||
expect(logo.text()).toContain("Glowing Fiesta");
|
||||
});
|
||||
|
||||
it("renders the menu toggle button", () => {
|
||||
const menuButton = wrapper.find(".layout-menu-button");
|
||||
expect(menuButton.exists()).toBe(true);
|
||||
expect(menuButton.find(".pi-bars").exists()).toBe(true);
|
||||
});
|
||||
|
||||
it("renders the dark mode toggle button", () => {
|
||||
const darkModeButton = wrapper.find(".layout-topbar-action-highlight");
|
||||
expect(darkModeButton.exists()).toBe(true);
|
||||
});
|
||||
|
||||
it("renders the topbar menu items", () => {
|
||||
const menuButtons = wrapper.findAll(".layout-topbar-menu button");
|
||||
expect(menuButtons).toHaveLength(3);
|
||||
expect(menuButtons[0].text()).toContain("Calendar");
|
||||
expect(menuButtons[1].text()).toContain("Messages");
|
||||
expect(menuButtons[2].text()).toContain("Profile");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Dark Mode Toggle", () => {
|
||||
it("starts with sun icon (light mode)", () => {
|
||||
const darkModeButton = wrapper.find(".layout-topbar-action-highlight");
|
||||
expect(darkModeButton.find(".pi-sun").exists()).toBe(true);
|
||||
expect(darkModeButton.find(".pi-moon").exists()).toBe(false);
|
||||
});
|
||||
|
||||
it("toggles to moon icon when clicked", async () => {
|
||||
const darkModeButton = wrapper.find(".layout-topbar-action-highlight");
|
||||
await darkModeButton.trigger("click");
|
||||
|
||||
expect(darkModeButton.find(".pi-moon").exists()).toBe(true);
|
||||
expect(darkModeButton.find(".pi-sun").exists()).toBe(false);
|
||||
});
|
||||
|
||||
it("removes app-dark class when toggled off", async () => {
|
||||
expect(document.documentElement.classList.contains("app-dark")).toBe(false);
|
||||
const darkModeButton = wrapper.find(".layout-topbar-action-highlight");
|
||||
|
||||
// Toggle on
|
||||
await darkModeButton.trigger("click");
|
||||
expect(document.documentElement.classList.contains("app-dark")).toBe(true);
|
||||
|
||||
// Toggle off
|
||||
await darkModeButton.trigger("click");
|
||||
expect(document.documentElement.classList.contains("app-dark")).toBe(false);
|
||||
});
|
||||
|
||||
it("updates isDarkTheme ref when toggled", async () => {
|
||||
const darkModeButton = wrapper.find(".layout-topbar-action-highlight");
|
||||
|
||||
expect(wrapper.vm.isDarkTheme).toBe(false);
|
||||
|
||||
await darkModeButton.trigger("click");
|
||||
expect(wrapper.vm.isDarkTheme).toBe(true);
|
||||
|
||||
await darkModeButton.trigger("click");
|
||||
expect(wrapper.vm.isDarkTheme).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Menu Toggle", () => {
|
||||
it("toggles layout-static-inactive class on layout wrapper", async () => {
|
||||
const menuButton = wrapper.find(".layout-menu-button");
|
||||
const layoutWrapper = document.querySelector(".layout-wrapper");
|
||||
|
||||
expect(layoutWrapper?.classList.contains("layout-static-inactive")).toBe(false);
|
||||
|
||||
await menuButton.trigger("click");
|
||||
expect(layoutWrapper?.classList.contains("layout-static-inactive")).toBe(true);
|
||||
|
||||
await menuButton.trigger("click");
|
||||
expect(layoutWrapper?.classList.contains("layout-static-inactive")).toBe(false);
|
||||
});
|
||||
|
||||
it("handles missing layout wrapper gracefully", async () => {
|
||||
document.body.innerHTML = "";
|
||||
|
||||
const menuButton = wrapper.find(".layout-menu-button");
|
||||
|
||||
// Should not throw error
|
||||
expect(() => menuButton.trigger("click")).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Logo Link", () => {
|
||||
it("links to home page", () => {
|
||||
const logoLink = wrapper.find(".layout-topbar-logo");
|
||||
expect(logoLink.exists()).toBe(true);
|
||||
});
|
||||
|
||||
it("has correct image source", () => {
|
||||
const img = wrapper.find(".layout-topbar-logo img");
|
||||
expect(img.attributes("src")).toContain("logo.png");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Topbar Actions", () => {
|
||||
it("renders Calendar action button with icon", () => {
|
||||
const buttons = wrapper.findAll(".layout-topbar-menu button");
|
||||
const calendarButton = buttons[0];
|
||||
|
||||
expect(calendarButton.find(".pi-calendar").exists()).toBe(true);
|
||||
expect(calendarButton.text()).toContain("Calendar");
|
||||
});
|
||||
|
||||
it("renders Messages action button with icon", () => {
|
||||
const buttons = wrapper.findAll(".layout-topbar-menu button");
|
||||
const messagesButton = buttons[1];
|
||||
|
||||
expect(messagesButton.find(".pi-inbox").exists()).toBe(true);
|
||||
expect(messagesButton.text()).toContain("Messages");
|
||||
});
|
||||
|
||||
it("renders Profile action button with icon", () => {
|
||||
const buttons = wrapper.findAll(".layout-topbar-menu button");
|
||||
const profileButton = buttons[2];
|
||||
|
||||
expect(profileButton.find(".pi-user").exists()).toBe(true);
|
||||
expect(profileButton.text()).toContain("Profile");
|
||||
});
|
||||
});
|
||||
});
|
||||
11
tests/pages/config/Account.test.ts
Normal file
11
tests/pages/config/Account.test.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { mount, type VueWrapper } from "@vue/test-utils";
|
||||
import AccountPage from "~/pages/config/Account.vue";
|
||||
|
||||
describe("Account.vue", () => {
|
||||
const wrapper: VueWrapper = mount(AccountPage, {});
|
||||
|
||||
it("loads without crashing", () => {
|
||||
expect(wrapper.exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
11
tests/pages/config/Settings.test.ts
Normal file
11
tests/pages/config/Settings.test.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { mount, type VueWrapper } from "@vue/test-utils";
|
||||
import SettingsPage from "~/pages/config/Settings.vue";
|
||||
|
||||
describe("Settings.vue", () => {
|
||||
const wrapper: VueWrapper = mount(SettingsPage, {});
|
||||
|
||||
it("loads without crashing", () => {
|
||||
expect(wrapper.exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { mount, type VueWrapper } from "@vue/test-utils";
|
||||
import IndexPage from "~/app/pages/index.vue";
|
||||
import IndexPage from "~/pages/index.vue";
|
||||
|
||||
describe("pages/index.vue", () => {
|
||||
const wrapper: VueWrapper = mount(IndexPage, {});
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { vi } from "vitest";
|
|||
import { config } from "@vue/test-utils";
|
||||
import PrimeVue from "primevue/config";
|
||||
import Button from "primevue/button";
|
||||
import Ripple from "primevue/ripple";
|
||||
|
||||
Object.defineProperty(global, "import", {
|
||||
value: {
|
||||
|
|
@ -13,5 +14,11 @@ Object.defineProperty(global, "import", {
|
|||
});
|
||||
|
||||
config.global.plugins = [PrimeVue];
|
||||
config.global.stubs = { NuxtPage: true };
|
||||
config.global.stubs = {
|
||||
NuxtLayout: true,
|
||||
NuxtPage: true,
|
||||
Divider: true,
|
||||
NuxtLink: { template: "<a><slot /></a>" },
|
||||
};
|
||||
config.global.components = { Button };
|
||||
config.global.directives = { Ripple };
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ export default defineConfig({
|
|||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"~": fileURLToPath(new URL("./", import.meta.url)),
|
||||
"@": fileURLToPath(new URL("./", import.meta.url)),
|
||||
"~": fileURLToPath(new URL("./app", import.meta.url)),
|
||||
"@": fileURLToPath(new URL("./app", import.meta.url)),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue