Begin switching from daisyui to shadcnui (#492)

* feat: add shadcn

* feat: add themes

* feat: make sidebar use shadcn

* feat: sort bg

* feat: lint fixes

* feat: make daisyui toggleable, add tooltips to sidebar, add work in progress docs page

* fix: theme switching for shadcn

* Fix minor profile.vue issue

* feat: update docs, enlarge SidebarMenuButton and refine profile layout

* feat: add testing page

* feat: update css and remove comments from template

* fix: create dropdown not opening due to tooltip interference also lint

* fix: correct CSS selector for homebox in main.css to ensure proper theming functionality

* feat: make theme switching actually kinda work for shadcn

* fix: sidebar colours

* fix: remove unused router import, made sidebar indicate active page and sort tailwind config linting

* style: update styles

* chore: remove unused duplicate code

* style: refine theme management, CSS variables, get styles closer to original

* feat: implement suggested changes

* feat: better button size

---------

Co-authored-by: Matt Kilgore <tankerkiller125@users.noreply.github.com>
This commit is contained in:
Tonya
2025-02-01 10:32:10 +00:00
committed by GitHub
parent 574079437a
commit e708bd9839
77 changed files with 2988 additions and 123 deletions

View File

@@ -0,0 +1,60 @@
# Shadcn-Vue
[Shadcn-Vue](https://www.shadcn-vue.com/) is a collection of Vue components based on [shadcn/ui](https://ui.shadcn.com/). We are currently in the process of migrating from DaisyUI to Shadcn-Vue for our component system.
## What is shadcn-vue?
To quote shadcn-vue:
> This is NOT a component library. It's a collection of re-usable components that you can copy and paste or use the CLI to add to your apps.
> What do you mean not a component library?
> It means you do not install it as a dependency. It is not available or distributed via npm, with no plans to publish it.
> Pick the components you need. Use the CLI to automatically add the components, or copy and paste the code into your project and customize to your needs. The code is yours.
The key advantage of this approach is that we have full control over the components and can modify them to suit our specific needs without being constrained by a third-party dependency.
## Adding Components
1. Add components using the CLI:
```bash
pnpx shadcn-vue@latest add [component-name]
```
For example:
```bash
pnpx shadcn-vue@latest add button
```
2. The components will be added to the route in the `components/ui` it then needs to be moved to `frontend/components/ui` for use.
## Usage
1. Import components from the components directory:
```vue
import { Button } from '@/components/ui/button'
```
2. Components can be used with their respective props and slots as documented in the shadcn-vue documentation.
## Modifying Components
When modifying components, follow these best practices:
1. If you need to modify a component for a specific use case:
- Copy the component and give it a name that reflects its purpose
- Keep the original shadcn component intact for other uses
2. When making global changes:
- Modify the component in the `components/ui` directory
- Document any significant changes in comments
## Testing without DaisyUI
During the migration process, you can test without DaisyUI using these commands:
```bash
DISABLE_DAISYUI=true; task ui:dev
```
or
```bash
DISABLE_DAISYUI=true; task ui:fix
```

View File

@@ -1,3 +1,778 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root,.homebox {
--background: 0 0% 100%; /* base 100 */
--foreground: 0 0% 20%; /* base content */
--muted: 0 0% 81%; /* base 300 */
--muted-foreground: 0 0% 20%; /* base content */
--popover: 0 0% 100%; /* base 100 */
--popover-foreground: 0 0% 20%; /* base content */
--card: 0 0% 100%; /* base 100 */
--card-foreground: 0 0% 20%; /* base content */
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--primary: 139 16% 43%; /* primary */
--primary-foreground: 139 100% 89%; /* primary text */
--secondary: 97 37% 93%; /* secondary */
--secondary-foreground: 97 31% 19%; /* secondary text */
--accent: 47 100% 67%; /* accent */
--accent-foreground: 47 100% 13%; /* accent text */
--destructive: 0 84.2% 60.2%; /* error */
--destructive-foreground: 210 40% 98%; /* error text */
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
--sidebar-background: var(--background);
--sidebar-foreground: var(--foreground);
--sidebar-primary: var(--primary);
--sidebar-primary-foreground: var(--primary-foreground);
--sidebar-accent: var(--accent);
--sidebar-accent-foreground: var(--accent-foreground);
--sidebar-border: var(--border);
--sidebar-ring: var(--ring);
}
/*
* The below themes are based on the daisyUI themes which are licensed under the MIT License.
* Copyright (c) 2020 Pouya Saadeghi
* The license can be found here https://github.com/saadeghi/daisyui
*
* The themes were converted to CSS variables by n0acar and licensed under the MIT License.
* Copyright (c) 2024 n0acar
* The license can be found here https://github.com/n0acar/tiny-projects
*/
.theme-aqua {
--border: 219 11% 89%;
--input: 219 11% 89%;
--ring: 219 11% 89%;
--background: 219 53% 43%;
--foreground: 219 11% 89%;
--primary: 182 93% 49%;
--primary-foreground: 181 100% 17%;
--secondary: 274 31% 57%;
--secondary-foreground: 274 6% 11%;
--destructive: 5 100% 70%;
--destructive-foreground: 5 24% 15%;
--muted: 219 45% 37%;
--muted-foreground: 219 11% 89%;
--accent: 47 100% 80%;
--accent-foreground: 47 20% 16%;
--popover: 219 53% 43%;
--popover-foreground: 219 11% 89%;
--card: 219 53% 43%;
--card-foreground: 219 11% 89%;
--radius: 0.5rem;
}
.theme-black {
--border: 224 0% 84%;
--input: 224 0% 84%;
--ring: 224 0% 84%;
--background: 0 0% 0%;
--foreground: 224 0% 84%;
--primary: 224 0% 22%;
--primary-foreground: 0 0% 84%;
--secondary: 224 0% 22%;
--secondary-foreground: 0 0% 84%;
--destructive: 0 100% 50%;
--destructive-foreground: 0 20% 10%;
--muted: 224 0% 15%;
--muted-foreground: 224 0% 84%;
--accent: 224 0% 22%;
--accent-foreground: 0 0% 84%;
--popover: 0 0% 0%;
--popover-foreground: 224 0% 84%;
--card: 0 0% 0%;
--card-foreground: 224 0% 84%;
--radius: 0;
}
.theme-bumblebee {
--border: 224 -35% 20%;
--input: 224 -35% 20%;
--ring: 224 -35% 20%;
--background: 0 0% 100%;
--foreground: 224 -35% 20%;
--primary: 51 100% 50%;
--primary-foreground: 49 31% 23%;
--secondary: 39 100% 50%;
--secondary-foreground: 34 59% 23%;
--destructive: 358.25 100% 69%;
--destructive-foreground: 0 0% 0%;
--muted: 224 -148% 86%;
--muted-foreground: 224 -35% 20%;
--accent: 28 100% 67%;
--accent-foreground: 27 24% 14%;
--popover: 0 0% 100%;
--popover-foreground: 224 -35% 20%;
--card: 0 0% 100%;
--card-foreground: 224 -35% 20%;
--radius: 0.5rem;
}
.theme-cmyk {
--border: 224 -35% 20%;
--input: 224 -35% 20%;
--ring: 224 -35% 20%;
--background: 0 0% 100%;
--foreground: 224 -35% 20%;
--primary: 203 83% 60%;
--primary-foreground: 203 17% 12%;
--secondary: 335 78% 60%;
--secondary-foreground: 335 16% 12%;
--destructive: 4 81% 56%;
--destructive-foreground: 4 16% 11%;
--muted: 224 -148% 86%;
--muted-foreground: 224 -35% 20%;
--accent: 56 100% 60%;
--accent-foreground: 56 20% 12%;
--popover: 0 0% 100%;
--popover-foreground: 224 -35% 20%;
--card: 0 0% 100%;
--card-foreground: 224 -35% 20%;
--radius: 0.5rem;
}
.theme-corporate {
--border: 233 27% 13%;
--input: 233 27% 13%;
--ring: 233 27% 13%;
--background: 0 0% 100%;
--foreground: 233 27% 13%;
--primary: 229 100% 65%;
--primary-foreground: 229 22% 13%;
--secondary: 215 26% 59%;
--secondary-foreground: 215 5% 12%;
--destructive: 358.25 100% 69%;
--destructive-foreground: 0 0% 0%;
--muted: 224 -148% 86%;
--muted-foreground: 233 27% 13%;
--accent: 154 49% 60%;
--accent-foreground: 154 10% 12%;
--popover: 0 0% 100%;
--popover-foreground: 233 27% 13%;
--card: 0 0% 100%;
--card-foreground: 233 27% 13%;
--radius: 0.125rem;
}
.theme-cupcake {
--border: 280 46% 14%;
--input: 280 46% 14%;
--ring: 280 46% 14%;
--background: 24 33% 97%;
--foreground: 280 46% 14%;
--primary: 183 47% 59%;
--primary-foreground: 183 9% 12%;
--secondary: 338 71% 78%;
--secondary-foreground: 338 14% 16%;
--destructive: 358.25 100% 69%;
--destructive-foreground: 0 0% 0%;
--muted: 23 14% 89%;
--muted-foreground: 280 46% 14%;
--accent: 39 84% 58%;
--accent-foreground: 39 17% 12%;
--popover: 24 33% 97%;
--popover-foreground: 280 46% 14%;
--card: 24 33% 97%;
--card-foreground: 280 46% 14%;
--radius: 1.9rem;
}
.theme-cyberpunk {
--border: 55 20% 13%;
--input: 55 20% 13%;
--ring: 55 20% 13%;
--background: 56 100% 64%;
--foreground: 55 20% 13%;
--primary: 343 100% 72%;
--primary-foreground: 343 26% 15%;
--secondary: 185 100% 49%;
--secondary-foreground: 184 50% 6%;
--destructive: 358.25 100% 69%;
--destructive-foreground: 0 0% 0%;
--muted: 55 88% 55%;
--muted-foreground: 55 20% 13%;
--accent: 279 100% 73%;
--accent-foreground: 277 22% 15%;
--popover: 56 100% 64%;
--popover-foreground: 55 20% 13%;
--card: 56 100% 64%;
--card-foreground: 55 20% 13%;
--radius: 0;
}
.theme-dark {
--border: 220 13% 69%;
--input: 220 13% 69%;
--ring: 220 13% 69%;
--background: 212 18% 14%;
--foreground: 220 13% 69%;
--primary: 235 100% 73%;
--primary-foreground: 235 22% 15%;
--secondary: 316 100% 69%;
--secondary-foreground: 318 25% 14%;
--destructive: 358.25 100% 69%;
--destructive-foreground: 0 0% 0%;
--muted: 213 18% 10%;
--muted-foreground: 220 13% 69%;
--accent: 174 100% 40%;
--accent-foreground: 176 51% 5%;
--popover: 212 18% 14%;
--popover-foreground: 220 13% 69%;
--card: 212 18% 14%;
--card-foreground: 220 13% 69%;
--radius: 0.5rem;
}
.theme-dracula {
--border: 60 30% 96%;
--input: 60 30% 96%;
--ring: 60 30% 96%;
--background: 231 15% 18%;
--foreground: 60 30% 96%;
--primary: 326 100% 74%;
--primary-foreground: 326 20% 15%;
--secondary: 265 89% 78%;
--secondary-foreground: 265 18% 16%;
--destructive: 0 100% 67%;
--destructive-foreground: 0 20% 13%;
--muted: 231 13% 16%;
--muted-foreground: 60 30% 96%;
--accent: 31 100% 71%;
--accent-foreground: 31 20% 14%;
--popover: 231 15% 18%;
--popover-foreground: 60 30% 96%;
--card: 231 15% 18%;
--card-foreground: 60 30% 96%;
--radius: 0.5rem;
}
.theme-emerald {
--border: 219 20% 25%;
--input: 219 20% 25%;
--ring: 219 20% 25%;
--background: 0 0% 100%;
--foreground: 219 20% 25%;
--primary: 141 50% 60%;
--primary-foreground: 151 28% 19%;
--secondary: 219 96% 60%;
--secondary-foreground: 180 100% 100%;
--destructive: 358.25 100% 69%;
--destructive-foreground: 0 0% 0%;
--muted: 224 -148% 86%;
--muted-foreground: 219 20% 25%;
--accent: 10 89% 68%;
--accent-foreground: 0 0% 0%;
--popover: 0 0% 100%;
--popover-foreground: 219 20% 25%;
--card: 0 0% 100%;
--card-foreground: 219 20% 25%;
--radius: 0.5rem;
}
.theme-fantasy {
--border: 215 28% 17%;
--input: 215 28% 17%;
--ring: 215 28% 17%;
--background: 0 0% 100%;
--foreground: 215 28% 17%;
--primary: 296 100% 23%;
--primary-foreground: 296 26% 84%;
--secondary: 203 100% 37%;
--secondary-foreground: 199 35% 86%;
--destructive: 358.25 100% 69%;
--destructive-foreground: 0 0% 0%;
--muted: 224 -148% 86%;
--muted-foreground: 215 28% 17%;
--accent: 32 100% 50%;
--accent-foreground: 35 29% 9%;
--popover: 0 0% 100%;
--popover-foreground: 215 28% 17%;
--card: 0 0% 100%;
--card-foreground: 215 28% 17%;
--radius: 0.5rem;
}
.theme-forest {
--border: 0 2% 82%;
--input: 0 2% 82%;
--ring: 0 2% 82%;
--background: 0 12% 8%;
--foreground: 0 2% 82%;
--primary: 141 72% 42%;
--primary-foreground: 0 0% 0%;
--secondary: 164 73% 42%;
--secondary-foreground: 164 15% 8%;
--destructive: 358.25 100% 69%;
--destructive-foreground: 0 0% 0%;
--muted: 0 10% 7%;
--muted-foreground: 0 2% 82%;
--accent: 175 73% 42%;
--accent-foreground: 175 15% 8%;
--popover: 0 12% 8%;
--popover-foreground: 0 2% 82%;
--card: 0 12% 8%;
--card-foreground: 0 2% 82%;
--radius: 1.9rem;
}
.theme-garden {
--border: 0 3% 6%;
--input: 0 3% 6%;
--ring: 0 3% 6%;
--background: 0 4% 91%;
--foreground: 0 3% 6%;
--primary: 332 100% 49%;
--primary-foreground: 180 100% 100%;
--secondary: 334 37% 41%;
--secondary-foreground: 334 7% 88%;
--destructive: 358.25 100% 69%;
--destructive-foreground: 0 0% 0%;
--muted: 0 4% 78%;
--muted-foreground: 0 3% 6%;
--accent: 139 16% 43%;
--accent-foreground: 139 3% 9%;
--popover: 0 4% 91%;
--popover-foreground: 0 3% 6%;
--card: 0 4% 91%;
--card-foreground: 0 3% 6%;
--radius: 0.5rem;
}
.theme-halloween {
--border: 0 0% 83%;
--input: 0 0% 83%;
--ring: 0 0% 83%;
--background: 224 0% 13%;
--foreground: 0 0% 83%;
--primary: 34 100% 50%;
--primary-foreground: 180 7% 8%;
--secondary: 278 100% 38%;
--secondary-foreground: 279 24% 87%;
--destructive: 3 87% 62%;
--destructive-foreground: 3 17% 12%;
--muted: 0 0% 11%;
--muted-foreground: 0 0% 83%;
--accent: 96 100% 33%;
--accent-foreground: 0 0% 0%;
--popover: 224 0% 13%;
--popover-foreground: 0 0% 83%;
--card: 224 0% 13%;
--card-foreground: 0 0% 83%;
--radius: 0.5rem;
}
.theme-light {
--border: 215 28% 17%;
--input: 215 28% 17%;
--ring: 215 28% 17%;
--background: 0 0% 100%;
--foreground: 215 28% 17%;
--primary: 257 100% 50%;
--primary-foreground: 258 22% 90%;
--secondary: 313 100% 56%;
--secondary-foreground: 320 100% 99%;
--destructive: 358.25 100% 69%;
--destructive-foreground: 0 0% 0%;
--muted: 180 2% 90%;
--muted-foreground: 215 28% 17%;
--accent: 174 100% 41%;
--accent-foreground: 176 59% 4%;
--popover: 0 0% 100%;
--popover-foreground: 215 28% 17%;
--card: 0 0% 100%;
--card-foreground: 215 28% 17%;
--radius: 0.5rem;
}
.theme-lofi {
--border: 0 0% 0%;
--input: 0 0% 0%;
--ring: 0 0% 0%;
--background: 0 0% 100%;
--foreground: 0 0% 0%;
--primary: 224 0% 5%;
--primary-foreground: 0 0% 100%;
--secondary: 0 2% 10%;
--secondary-foreground: 0 0% 100%;
--destructive: 7 100% 76%;
--destructive-foreground: 7 24% 16%;
--muted: 0 2% 90%;
--muted-foreground: 0 0% 0%;
--accent: 224 0% 15%;
--accent-foreground: 0 0% 100%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 0%;
--card: 0 0% 100%;
--card-foreground: 0 0% 0%;
--radius: 0.125rem;
}
.theme-luxury {
--border: 37 67% 58%;
--input: 37 67% 58%;
--ring: 37 67% 58%;
--background: 240 10% 4%;
--foreground: 37 67% 58%;
--primary: 0 0% 100%;
--primary-foreground: 224 -35% 20%;
--secondary: 218 54% 18%;
--secondary-foreground: 218 11% 84%;
--destructive: 0 100% 72%;
--destructive-foreground: 0 20% 14%;
--muted: 270 2% 18%;
--muted-foreground: 37 67% 58%;
--accent: 319 22% 26%;
--accent-foreground: 319 4% 85%;
--popover: 240 10% 4%;
--popover-foreground: 37 67% 58%;
--card: 240 10% 4%;
--card-foreground: 37 67% 58%;
--radius: 0.5rem;
}
.theme-pastel {
--border: 224 -35% 20%;
--input: 224 -35% 20%;
--ring: 224 -35% 20%;
--background: 0 0% 100%;
--foreground: 224 -35% 20%;
--primary: 284 22% 80%;
--primary-foreground: 284 4% 16%;
--secondary: 352 70% 88%;
--secondary-foreground: 352 14% 18%;
--destructive: 358.25 100% 69%;
--destructive-foreground: 0 0% 0%;
--muted: 216 12% 84%;
--muted-foreground: 224 -35% 20%;
--accent: 158 55% 81%;
--accent-foreground: 158 11% 16%;
--popover: 0 0% 100%;
--popover-foreground: 224 -35% 20%;
--card: 0 0% 100%;
--card-foreground: 224 -35% 20%;
--radius: 1.9rem;
}
.theme-retro {
--border: 345 5% 15%;
--input: 345 5% 15%;
--ring: 345 5% 15%;
--background: 44 47% 86%;
--foreground: 345 5% 15%;
--primary: 3 74% 76%;
--primary-foreground: 345 5% 15%;
--secondary: 145 27% 72%;
--secondary-foreground: 345 5% 15%;
--destructive: 3 87% 62%;
--destructive-foreground: 3 17% 12%;
--muted: 44 47% 73%;
--muted-foreground: 345 5% 15%;
--accent: 24 67% 59%;
--accent-foreground: 345 5% 15%;
--popover: 44 47% 86%;
--popover-foreground: 345 5% 15%;
--card: 44 47% 86%;
--card-foreground: 345 5% 15%;
--radius: 0.4rem;
}
.theme-synthwave {
--border: 260 60% 98%;
--input: 260 60% 98%;
--ring: 260 60% 98%;
--background: 253 58% 15%;
--foreground: 260 60% 98%;
--primary: 321 70% 69%;
--primary-foreground: 321 14% 14%;
--secondary: 197 87% 65%;
--secondary-foreground: 197 17% 13%;
--destructive: 10 75% 70%;
--destructive-foreground: 257 63% 17%;
--muted: 253 50% 13%;
--muted-foreground: 260 60% 98%;
--accent: 50 100% 50%;
--accent-foreground: 51 35% 7%;
--popover: 253 58% 15%;
--popover-foreground: 260 60% 98%;
--card: 253 58% 15%;
--card-foreground: 260 60% 98%;
--radius: 0.5rem;
}
.theme-valentine {
--border: 344 38% 28%;
--input: 344 38% 28%;
--ring: 344 38% 28%;
--background: 319 66% 94%;
--foreground: 344 38% 28%;
--primary: 353 74% 67%;
--primary-foreground: 353 15% 13%;
--secondary: 254 86% 77%;
--secondary-foreground: 254 17% 15%;
--destructive: 5 100% 69%;
--destructive-foreground: 4 25% 14%;
--muted: 319 56% 81%;
--muted-foreground: 344 38% 28%;
--accent: 182 34% 55%;
--accent-foreground: 182 7% 11%;
--popover: 319 66% 94%;
--popover-foreground: 344 38% 28%;
--card: 319 66% 94%;
--card-foreground: 344 38% 28%;
--radius: 1.9rem;
}
.theme-wireframe {
--border: 224 -35% 20%;
--input: 224 -35% 20%;
--ring: 224 -35% 20%;
--background: 0 0% 100%;
--foreground: 224 -35% 20%;
--primary: 224 0% 72%;
--primary-foreground: 0 0% 14%;
--secondary: 224 0% 72%;
--secondary-foreground: 0 0% 14%;
--destructive: 0 100% 50%;
--destructive-foreground: 0 20% 10%;
--muted: 224 0% 87%;
--muted-foreground: 224 -35% 20%;
--accent: 224 0% 72%;
--accent-foreground: 0 0% 14%;
--popover: 0 0% 100%;
--popover-foreground: 224 -35% 20%;
--card: 0 0% 100%;
--card-foreground: 224 -35% 20%;
--radius: 0.2rem;
}
.theme-autumn {
--border: 0 0% 19%;
--input: 0 0% 19%;
--ring: 0 0% 19%;
--background: 224 0% 95%;
--foreground: 0 0% 19%;
--primary: 344 96% 28%;
--primary-foreground: 344 19% 86%;
--secondary: 0 63% 58%;
--secondary-foreground: 0 13% 12%;
--destructive: 353 100% 41%;
--destructive-foreground: 346 29% 87%;
--muted: 0 0% 81%;
--muted-foreground: 0 0% 19%;
--accent: 27 56% 63%;
--accent-foreground: 27 11% 13%;
--popover: 224 0% 95%;
--popover-foreground: 0 0% 19%;
--card: 224 0% 95%;
--card-foreground: 0 0% 19%;
--radius: 0.5rem;
}
.theme-business {
--border: 0 0% 83%;
--input: 0 0% 83%;
--ring: 0 0% 83%;
--background: 224 0% 13%;
--foreground: 0 0% 83%;
--primary: 210 64% 31%;
--primary-foreground: 210 13% 86%;
--secondary: 200 13% 55%;
--secondary-foreground: 200 3% 11%;
--destructive: 6 56% 43%;
--destructive-foreground: 6 11% 89%;
--muted: 0 0% 11%;
--muted-foreground: 0 0% 83%;
--accent: 13 80% 60%;
--accent-foreground: 13 16% 12%;
--popover: 224 0% 13%;
--popover-foreground: 0 0% 83%;
--card: 224 0% 13%;
--card-foreground: 0 0% 83%;
--radius: 0.125rem;
}
.theme-acid {
--border: 0 0% 20%;
--input: 0 0% 20%;
--ring: 0 0% 20%;
--background: 224 0% 98%;
--foreground: 0 0% 20%;
--primary: 300 100% 53%;
--primary-foreground: 302 30% 9%;
--secondary: 28 100% 50%;
--secondary-foreground: 30 29% 9%;
--destructive: 2 100% 51%;
--destructive-foreground: 357 29% 9%;
--muted: 0 0% 84%;
--muted-foreground: 0 0% 20%;
--accent: 73 100% 50%;
--accent-foreground: 70 39% 7%;
--popover: 224 0% 98%;
--popover-foreground: 0 0% 20%;
--card: 224 0% 98%;
--card-foreground: 0 0% 20%;
--radius: 1rem;
}
.theme-lemonade {
--border: 83 16% 19%;
--input: 83 16% 19%;
--ring: 83 16% 19%;
--background: 83 82% 97%;
--foreground: 83 16% 19%;
--primary: 93 100% 29%;
--primary-foreground: 86 36% 4%;
--secondary: 61 100% 38%;
--secondary-foreground: 61 39% 5%;
--destructive: 6 59% 85%;
--destructive-foreground: 6 12% 17%;
--muted: 83 70% 83%;
--muted-foreground: 83 16% 19%;
--accent: 53 100% 46%;
--accent-foreground: 54 35% 7%;
--popover: 83 82% 97%;
--popover-foreground: 83 16% 19%;
--card: 83 82% 97%;
--card-foreground: 83 16% 19%;
--radius: 0.5rem;
}
.theme-night {
--border: 222 9% 82%;
--input: 222 9% 82%;
--ring: 222 9% 82%;
--background: 222 47% 11%;
--foreground: 222 9% 82%;
--primary: 198 93% 60%;
--primary-foreground: 198 19% 12%;
--secondary: 234 89% 74%;
--secondary-foreground: 234 18% 15%;
--destructive: 351 95% 71%;
--destructive-foreground: 351 19% 14%;
--muted: 222 41% 10%;
--muted-foreground: 222 9% 82%;
--accent: 329 86% 70%;
--accent-foreground: 329 17% 14%;
--popover: 222 47% 11%;
--popover-foreground: 222 9% 82%;
--card: 222 47% 11%;
--card-foreground: 222 9% 82%;
--radius: 0.5rem;
}
.theme-coffee {
--border: 37 47% 57%;
--input: 37 47% 57%;
--ring: 37 47% 57%;
--background: 306 19% 11%;
--foreground: 37 47% 57%;
--primary: 30 67% 58%;
--primary-foreground: 30 13% 12%;
--secondary: 182 25% 20%;
--secondary-foreground: 182 5% 84%;
--destructive: 10 95% 75%;
--destructive-foreground: 10 19% 15%;
--muted: 306 16% 9%;
--muted-foreground: 37 47% 57%;
--accent: 194 74% 25%;
--accent-foreground: 194 15% 85%;
--popover: 306 19% 11%;
--popover-foreground: 37 47% 57%;
--card: 306 19% 11%;
--card-foreground: 37 47% 57%;
--radius: 0.5rem;
}
.theme-winter {
--border: 214 30% 32%;
--input: 214 30% 32%;
--ring: 214 30% 32%;
--background: 0 0% 100%;
--foreground: 214 30% 32%;
--primary: 215 100% 50%;
--primary-foreground: 211 28% 89%;
--secondary: 247 47% 43%;
--secondary-foreground: 247 9% 89%;
--destructive: 0 63% 72%;
--destructive-foreground: 0 13% 14%;
--muted: 219 44% 92%;
--muted-foreground: 214 30% 32%;
--accent: 310 49% 52%;
--accent-foreground: 310 10% 10%;
--popover: 0 0% 100%;
--popover-foreground: 214 30% 32%;
--card: 0 0% 100%;
--card-foreground: 214 30% 32%;
--radius: 0.5rem;
}
.theme-dim {
--border: 197 31% 77%;
--input: 197 31% 77%;
--ring: 197 31% 77%;
--background: 220 18% 20%;
--foreground: 197 31% 77%;
--primary: 108 66% 73%;
--primary-foreground: 108 13% 15%;
--secondary: 12 100% 68%;
--secondary-foreground: 12 20% 14%;
--destructive: 11 100% 80%;
--destructive-foreground: 11 20% 16%;
--muted: 219 18% 15%;
--muted-foreground: 197 31% 77%;
--accent: 277 66% 74%;
--accent-foreground: 277 13% 15%;
--popover: 220 18% 20%;
--popover-foreground: 197 31% 77%;
--card: 220 18% 20%;
--card-foreground: 197 31% 77%;
--radius: 0.5rem;
}
.theme-nord {
--border: 220 16% 22%;
--input: 220 16% 22%;
--ring: 220 16% 22%;
--background: 217 27% 94%;
--foreground: 220 16% 22%;
--primary: 213 32% 52%;
--primary-foreground: 213 6% 10%;
--secondary: 210 34% 63%;
--secondary-foreground: 210 7% 13%;
--destructive: 354 42% 56%;
--destructive-foreground: 354 8% 11%;
--muted: 219 28% 88%;
--muted-foreground: 220 16% 22%;
--accent: 193 43% 67%;
--accent-foreground: 193 9% 13%;
--popover: 217 27% 94%;
--popover-foreground: 220 16% 22%;
--card: 217 27% 94%;
--card-foreground: 220 16% 22%;
--radius: 0.2rem;
}
.theme-sunset {
--border: 208 34% 72%;
--input: 208 34% 72%;
--ring: 208 34% 72%;
--background: 204 31% 10%;
--foreground: 208 34% 72%;
--primary: 16 100% 68%;
--primary-foreground: 16 20% 14%;
--secondary: 341 97% 71%;
--secondary-foreground: 341 19% 14%;
--destructive: 358 100% 87%;
--destructive-foreground: 358 20% 17%;
--muted: 204 45% 7%;
--muted-foreground: 208 34% 72%;
--accent: 263 92% 75%;
--accent-foreground: 263 18% 15%;
--popover: 204 31% 10%;
--popover-foreground: 208 34% 72%;
--card: 204 31% 10%;
--card-foreground: 208 34% 72%;
--radius: 0.8rem;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
.text-no-transform { .text-no-transform {
text-transform: none !important; text-transform: none !important;
} }
@@ -35,7 +810,7 @@
.scroll-bg::-webkit-scrollbar-thumb { .scroll-bg::-webkit-scrollbar-thumb {
border-radius: 0.25rem; border-radius: 0.25rem;
@apply bg-base-300; @apply bg-muted;
} }
.markdown > :first-child { .markdown > :first-child {

18
frontend/components.json Normal file
View File

@@ -0,0 +1,18 @@
{
"$schema": "https://shadcn-vue.com/schema.json",
"style": "default",
"typescript": true,
"tsConfigPath": "tsconfig.json",
"tailwind": {
"config": "tailwind.config.js",
"css": "assets/css/main.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"framework": "nuxt",
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}

View File

@@ -0,0 +1,22 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue";
import { Primitive, type PrimitiveProps } from "radix-vue";
import { type ButtonVariants, buttonVariants } from ".";
import { cn } from "@/lib/utils";
interface Props extends PrimitiveProps {
variant?: ButtonVariants["variant"];
size?: ButtonVariants["size"];
class?: HTMLAttributes["class"];
}
const props = withDefaults(defineProps<Props>(), {
as: "button",
});
</script>
<template>
<Primitive :as="as" :as-child="asChild" :class="cn(buttonVariants({ variant, size }), props.class)">
<slot />
</Primitive>
</template>

View File

@@ -0,0 +1,31 @@
import { cva, type VariantProps } from "class-variance-authority";
export { default as Button } from "./Button.vue";
export const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 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-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);
export type ButtonVariants = VariantProps<typeof buttonVariants>;

View File

@@ -0,0 +1,19 @@
<script setup lang="ts">
import {
DropdownMenuRoot,
type DropdownMenuRootEmits,
type DropdownMenuRootProps,
useForwardPropsEmits,
} from "radix-vue";
const props = defineProps<DropdownMenuRootProps>();
const emits = defineEmits<DropdownMenuRootEmits>();
const forwarded = useForwardPropsEmits(props, emits);
</script>
<template>
<DropdownMenuRoot v-bind="forwarded">
<slot />
</DropdownMenuRoot>
</template>

View File

@@ -0,0 +1,42 @@
<script setup lang="ts">
import { Check } from "lucide-vue-next";
import {
DropdownMenuCheckboxItem,
type DropdownMenuCheckboxItemEmits,
type DropdownMenuCheckboxItemProps,
DropdownMenuItemIndicator,
useForwardPropsEmits,
} from "radix-vue";
import { computed, type HTMLAttributes } from "vue";
import { cn } from "@/lib/utils";
const props = defineProps<DropdownMenuCheckboxItemProps & { class?: HTMLAttributes["class"] }>();
const emits = defineEmits<DropdownMenuCheckboxItemEmits>();
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
const forwarded = useForwardPropsEmits(delegatedProps, emits);
</script>
<template>
<DropdownMenuCheckboxItem
v-bind="forwarded"
:class="
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',
props.class
)
"
>
<span class="absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuItemIndicator>
<Check class="size-4" />
</DropdownMenuItemIndicator>
</span>
<slot />
</DropdownMenuCheckboxItem>
</template>

View File

@@ -0,0 +1,40 @@
<script setup lang="ts">
import {
DropdownMenuContent,
type DropdownMenuContentEmits,
type DropdownMenuContentProps,
DropdownMenuPortal,
useForwardPropsEmits,
} from "radix-vue";
import { computed, type HTMLAttributes } from "vue";
import { cn } from "@/lib/utils";
const props = withDefaults(defineProps<DropdownMenuContentProps & { class?: HTMLAttributes["class"] }>(), {
sideOffset: 4,
});
const emits = defineEmits<DropdownMenuContentEmits>();
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
const forwarded = useForwardPropsEmits(delegatedProps, emits);
</script>
<template>
<DropdownMenuPortal>
<DropdownMenuContent
v-bind="forwarded"
:class="
cn(
'z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 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',
props.class
)
"
>
<slot />
</DropdownMenuContent>
</DropdownMenuPortal>
</template>

View File

@@ -0,0 +1,11 @@
<script setup lang="ts">
import { DropdownMenuGroup, type DropdownMenuGroupProps } from "radix-vue";
const props = defineProps<DropdownMenuGroupProps>();
</script>
<template>
<DropdownMenuGroup v-bind="props">
<slot />
</DropdownMenuGroup>
</template>

View File

@@ -0,0 +1,30 @@
<script setup lang="ts">
import { DropdownMenuItem, type DropdownMenuItemProps, useForwardProps } from "radix-vue";
import { computed, type HTMLAttributes } from "vue";
import { cn } from "@/lib/utils";
const props = defineProps<DropdownMenuItemProps & { class?: HTMLAttributes["class"]; inset?: boolean }>();
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
const forwardedProps = useForwardProps(delegatedProps);
</script>
<template>
<DropdownMenuItem
v-bind="forwardedProps"
:class="
cn(
'relative flex cursor-default select-none items-center rounded-sm gap-2 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 [&>svg]:size-4 [&>svg]:shrink-0',
inset && 'pl-8',
props.class
)
"
>
<slot />
</DropdownMenuItem>
</template>

View File

@@ -0,0 +1,24 @@
<script setup lang="ts">
import { DropdownMenuLabel, type DropdownMenuLabelProps, useForwardProps } from "radix-vue";
import { computed, type HTMLAttributes } from "vue";
import { cn } from "@/lib/utils";
const props = defineProps<DropdownMenuLabelProps & { class?: HTMLAttributes["class"]; inset?: boolean }>();
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
const forwardedProps = useForwardProps(delegatedProps);
</script>
<template>
<DropdownMenuLabel
v-bind="forwardedProps"
:class="cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', props.class)"
>
<slot />
</DropdownMenuLabel>
</template>

View File

@@ -0,0 +1,19 @@
<script setup lang="ts">
import {
DropdownMenuRadioGroup,
type DropdownMenuRadioGroupEmits,
type DropdownMenuRadioGroupProps,
useForwardPropsEmits,
} from "radix-vue";
const props = defineProps<DropdownMenuRadioGroupProps>();
const emits = defineEmits<DropdownMenuRadioGroupEmits>();
const forwarded = useForwardPropsEmits(props, emits);
</script>
<template>
<DropdownMenuRadioGroup v-bind="forwarded">
<slot />
</DropdownMenuRadioGroup>
</template>

View File

@@ -0,0 +1,43 @@
<script setup lang="ts">
import { Circle } from "lucide-vue-next";
import {
DropdownMenuItemIndicator,
DropdownMenuRadioItem,
type DropdownMenuRadioItemEmits,
type DropdownMenuRadioItemProps,
useForwardPropsEmits,
} from "radix-vue";
import { computed, type HTMLAttributes } from "vue";
import { cn } from "@/lib/utils";
const props = defineProps<DropdownMenuRadioItemProps & { class?: HTMLAttributes["class"] }>();
const emits = defineEmits<DropdownMenuRadioItemEmits>();
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
const forwarded = useForwardPropsEmits(delegatedProps, emits);
</script>
<template>
<DropdownMenuRadioItem
v-bind="forwarded"
:class="
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',
props.class
)
"
>
<span class="absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuItemIndicator>
<Circle class="size-2 fill-current" />
</DropdownMenuItemIndicator>
</span>
<slot />
</DropdownMenuRadioItem>
</template>

View File

@@ -0,0 +1,21 @@
<script setup lang="ts">
import { DropdownMenuSeparator, type DropdownMenuSeparatorProps } from "radix-vue";
import { computed, type HTMLAttributes } from "vue";
import { cn } from "@/lib/utils";
const props = defineProps<
DropdownMenuSeparatorProps & {
class?: HTMLAttributes["class"];
}
>();
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
</script>
<template>
<DropdownMenuSeparator v-bind="delegatedProps" :class="cn('-mx-1 my-1 h-px bg-muted', props.class)" />
</template>

View 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>
<span :class="cn('ml-auto text-xs tracking-widest opacity-60', props.class)">
<slot />
</span>
</template>

View File

@@ -0,0 +1,19 @@
<script setup lang="ts">
import {
DropdownMenuSub,
type DropdownMenuSubEmits,
type DropdownMenuSubProps,
useForwardPropsEmits,
} from "radix-vue";
const props = defineProps<DropdownMenuSubProps>();
const emits = defineEmits<DropdownMenuSubEmits>();
const forwarded = useForwardPropsEmits(props, emits);
</script>
<template>
<DropdownMenuSub v-bind="forwarded">
<slot />
</DropdownMenuSub>
</template>

View File

@@ -0,0 +1,35 @@
<script setup lang="ts">
import {
DropdownMenuSubContent,
type DropdownMenuSubContentEmits,
type DropdownMenuSubContentProps,
useForwardPropsEmits,
} from "radix-vue";
import { computed, type HTMLAttributes } from "vue";
import { cn } from "@/lib/utils";
const props = defineProps<DropdownMenuSubContentProps & { class?: HTMLAttributes["class"] }>();
const emits = defineEmits<DropdownMenuSubContentEmits>();
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
const forwarded = useForwardPropsEmits(delegatedProps, emits);
</script>
<template>
<DropdownMenuSubContent
v-bind="forwarded"
:class="
cn(
'z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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
)
"
>
<slot />
</DropdownMenuSubContent>
</template>

View File

@@ -0,0 +1,31 @@
<script setup lang="ts">
import { ChevronRight } from "lucide-vue-next";
import { DropdownMenuSubTrigger, type DropdownMenuSubTriggerProps, useForwardProps } from "radix-vue";
import { computed, type HTMLAttributes } from "vue";
import { cn } from "@/lib/utils";
const props = defineProps<DropdownMenuSubTriggerProps & { class?: HTMLAttributes["class"] }>();
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
const forwardedProps = useForwardProps(delegatedProps);
</script>
<template>
<DropdownMenuSubTrigger
v-bind="forwardedProps"
:class="
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',
props.class
)
"
>
<slot />
<ChevronRight class="ml-auto size-4" />
</DropdownMenuSubTrigger>
</template>

View File

@@ -0,0 +1,13 @@
<script setup lang="ts">
import { DropdownMenuTrigger, type DropdownMenuTriggerProps, useForwardProps } from "radix-vue";
const props = defineProps<DropdownMenuTriggerProps>();
const forwardedProps = useForwardProps(props);
</script>
<template>
<DropdownMenuTrigger class="outline-none" v-bind="forwardedProps">
<slot />
</DropdownMenuTrigger>
</template>

View File

@@ -0,0 +1,16 @@
export { default as DropdownMenu } from "./DropdownMenu.vue";
export { default as DropdownMenuCheckboxItem } from "./DropdownMenuCheckboxItem.vue";
export { default as DropdownMenuContent } from "./DropdownMenuContent.vue";
export { default as DropdownMenuGroup } from "./DropdownMenuGroup.vue";
export { default as DropdownMenuItem } from "./DropdownMenuItem.vue";
export { default as DropdownMenuLabel } from "./DropdownMenuLabel.vue";
export { default as DropdownMenuRadioGroup } from "./DropdownMenuRadioGroup.vue";
export { default as DropdownMenuRadioItem } from "./DropdownMenuRadioItem.vue";
export { default as DropdownMenuSeparator } from "./DropdownMenuSeparator.vue";
export { default as DropdownMenuShortcut } from "./DropdownMenuShortcut.vue";
export { default as DropdownMenuSub } from "./DropdownMenuSub.vue";
export { default as DropdownMenuSubContent } from "./DropdownMenuSubContent.vue";
export { default as DropdownMenuSubTrigger } from "./DropdownMenuSubTrigger.vue";
export { default as DropdownMenuTrigger } from "./DropdownMenuTrigger.vue";
export { DropdownMenuPortal } from "radix-vue";

View File

@@ -0,0 +1,32 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue";
import { useVModel } from "@vueuse/core";
import { cn } from "@/lib/utils";
const props = defineProps<{
defaultValue?: string | number;
modelValue?: string | number;
class?: HTMLAttributes["class"];
}>();
const emits = defineEmits<{
(e: "update:modelValue", payload: string | number): void;
}>();
const modelValue = useVModel(props, "modelValue", emits, {
passive: true,
defaultValue: props.defaultValue,
});
</script>
<template>
<input
v-model="modelValue"
:class="
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',
props.class
)
"
/>
</template>

View File

@@ -0,0 +1 @@
export { default as Input } from "./Input.vue";

View File

@@ -0,0 +1,33 @@
<script setup lang="ts">
import { Separator, type SeparatorProps } from "radix-vue";
import { computed, type HTMLAttributes } from "vue";
import { cn } from "@/lib/utils";
const props = defineProps<SeparatorProps & { class?: HTMLAttributes["class"]; label?: string }>();
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
</script>
<template>
<Separator
v-bind="delegatedProps"
:class="
cn('shrink-0 bg-border relative', props.orientation === 'vertical' ? 'w-px h-full' : 'h-px w-full', props.class)
"
>
<span
v-if="props.label"
:class="
cn(
'text-xs text-muted-foreground bg-background absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex justify-center items-center',
props.orientation === 'vertical' ? 'w-[1px] px-1 py-2' : 'h-[1px] py-1 px-2'
)
"
>{{ props.label }}</span
>
</Separator>
</template>

View File

@@ -0,0 +1 @@
export { default as Separator } from "./Separator.vue";

View File

@@ -0,0 +1,14 @@
<script setup lang="ts">
import { DialogRoot, type DialogRootEmits, type DialogRootProps, useForwardPropsEmits } from "radix-vue";
const props = defineProps<DialogRootProps>();
const emits = defineEmits<DialogRootEmits>();
const forwarded = useForwardPropsEmits(props, emits);
</script>
<template>
<DialogRoot v-bind="forwarded">
<slot />
</DialogRoot>
</template>

View File

@@ -0,0 +1,11 @@
<script setup lang="ts">
import { DialogClose, type DialogCloseProps } from "radix-vue";
const props = defineProps<DialogCloseProps>();
</script>
<template>
<DialogClose v-bind="props">
<slot />
</DialogClose>
</template>

View File

@@ -0,0 +1,53 @@
<script setup lang="ts">
import { X } from "lucide-vue-next";
import {
DialogClose,
DialogContent,
type DialogContentEmits,
type DialogContentProps,
DialogOverlay,
DialogPortal,
useForwardPropsEmits,
} from "radix-vue";
import { computed, type HTMLAttributes } from "vue";
import { type SheetVariants, sheetVariants } from ".";
import { cn } from "@/lib/utils";
interface SheetContentProps extends DialogContentProps {
class?: HTMLAttributes["class"];
side?: SheetVariants["side"];
}
defineOptions({
inheritAttrs: false,
});
const props = defineProps<SheetContentProps>();
const emits = defineEmits<DialogContentEmits>();
const delegatedProps = computed(() => {
const { class: _, side, ...delegated } = props;
return delegated;
});
const forwarded = useForwardPropsEmits(delegatedProps, emits);
</script>
<template>
<DialogPortal>
<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"
/>
<DialogContent :class="cn(sheetVariants({ side }), props.class)" v-bind="{ ...forwarded, ...$attrs }">
<slot />
<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-secondary"
>
<X class="size-4 text-muted-foreground" />
</DialogClose>
</DialogContent>
</DialogPortal>
</template>

View File

@@ -0,0 +1,19 @@
<script setup lang="ts">
import { DialogDescription, type DialogDescriptionProps } from "radix-vue";
import { computed, type HTMLAttributes } from "vue";
import { cn } from "@/lib/utils";
const props = defineProps<DialogDescriptionProps & { class?: HTMLAttributes["class"] }>();
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
</script>
<template>
<DialogDescription :class="cn('text-sm text-muted-foreground', props.class)" v-bind="delegatedProps">
<slot />
</DialogDescription>
</template>

View File

@@ -0,0 +1,12 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue";
import { cn } from "@/lib/utils";
const props = defineProps<{ class?: HTMLAttributes["class"] }>();
</script>
<template>
<div :class="cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:gap-x-2', props.class)">
<slot />
</div>
</template>

View File

@@ -0,0 +1,12 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue";
import { cn } from "@/lib/utils";
const props = defineProps<{ class?: HTMLAttributes["class"] }>();
</script>
<template>
<div :class="cn('flex flex-col gap-y-2 text-center sm:text-left', props.class)">
<slot />
</div>
</template>

View File

@@ -0,0 +1,19 @@
<script setup lang="ts">
import { DialogTitle, type DialogTitleProps } from "radix-vue";
import { computed, type HTMLAttributes } from "vue";
import { cn } from "@/lib/utils";
const props = defineProps<DialogTitleProps & { class?: HTMLAttributes["class"] }>();
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
</script>
<template>
<DialogTitle :class="cn('text-lg font-semibold text-foreground', props.class)" v-bind="delegatedProps">
<slot />
</DialogTitle>
</template>

View File

@@ -0,0 +1,11 @@
<script setup lang="ts">
import { DialogTrigger, type DialogTriggerProps } from "radix-vue";
const props = defineProps<DialogTriggerProps>();
</script>
<template>
<DialogTrigger v-bind="props">
<slot />
</DialogTrigger>
</template>

View File

@@ -0,0 +1,31 @@
import { cva, type VariantProps } from "class-variance-authority";
export { default as Sheet } from "./Sheet.vue";
export { default as SheetClose } from "./SheetClose.vue";
export { default as SheetContent } from "./SheetContent.vue";
export { default as SheetDescription } from "./SheetDescription.vue";
export { default as SheetFooter } from "./SheetFooter.vue";
export { default as SheetHeader } from "./SheetHeader.vue";
export { default as SheetTitle } from "./SheetTitle.vue";
export { default as SheetTrigger } from "./SheetTrigger.vue";
export const sheetVariants = cva(
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out",
{
variants: {
side: {
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
bottom:
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
right:
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
},
},
defaultVariants: {
side: "right",
},
}
);
export type SheetVariants = VariantProps<typeof sheetVariants>;

View File

@@ -0,0 +1,90 @@
<script setup lang="ts">
import { SIDEBAR_WIDTH_MOBILE, useSidebar } from "./utils";
import type { SidebarProps } from ".";
import { Sheet, SheetContent } from "@/components/ui/sheet";
import { cn } from "@/lib/utils";
defineOptions({
inheritAttrs: false,
});
const props = withDefaults(defineProps<SidebarProps>(), {
side: "left",
variant: "sidebar",
collapsible: "offcanvas",
});
const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
</script>
<template>
<div
v-if="collapsible === 'none'"
:class="cn('flex h-full w-[--sidebar-width] flex-col bg-sidebar text-sidebar-foreground', props.class)"
v-bind="$attrs"
>
<slot />
</div>
<Sheet v-else-if="isMobile" :open="openMobile" v-bind="$attrs" @update:open="setOpenMobile">
<SheetContent
data-sidebar="sidebar"
data-mobile="true"
:side="side"
class="bg-sidebar text-sidebar-foreground w-[--sidebar-width] p-0 [&>button]:hidden"
:style="{
'--sidebar-width': SIDEBAR_WIDTH_MOBILE,
}"
>
<div class="flex size-full flex-col">
<slot />
</div>
</SheetContent>
</Sheet>
<div
v-else
class="group peer hidden md:block"
:data-state="state"
:data-collapsible="state === 'collapsed' ? collapsible : ''"
:data-variant="variant"
:data-side="side"
>
<!-- This is what handles the sidebar gap on desktop -->
<div
:class="
cn(
'duration-200 relative h-svh w-[--sidebar-width] bg-transparent transition-[width] ease-linear',
'group-data-[collapsible=offcanvas]:w-0',
'group-data-[side=right]:rotate-180',
variant === 'floating' || variant === 'inset'
? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]'
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon]'
)
"
/>
<div
:class="
cn(
'duration-200 fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width] transition-[left,right,width] ease-linear md:flex',
side === 'left'
? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
: 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
// Adjust the padding for floating and inset variants.
variant === 'floating' || variant === 'inset'
? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]'
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon]',
props.class
)
"
v-bind="$attrs"
>
<div
data-sidebar="sidebar"
class="text-sidebar-foreground bg-sidebar group-data-[variant=floating]:border-sidebar-border flex size-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow"
>
<slot />
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,19 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue";
import { cn } from "@/lib/utils";
const props = defineProps<{
class?: HTMLAttributes["class"];
}>();
</script>
<template>
<div
data-sidebar="content"
:class="
cn('flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden', props.class)
"
>
<slot />
</div>
</template>

View 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>
<div data-sidebar="footer" :class="cn('flex flex-col gap-2 p-2', props.class)">
<slot />
</div>
</template>

View 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>
<div data-sidebar="group" :class="cn('relative flex w-full min-w-0 flex-col p-2', props.class)">
<slot />
</div>
</template>

View File

@@ -0,0 +1,31 @@
<script setup lang="ts">
import type { PrimitiveProps } from "radix-vue";
import type { HTMLAttributes } from "vue";
import { Primitive } from "radix-vue";
import { cn } from "@/lib/utils";
const props = defineProps<
PrimitiveProps & {
class?: HTMLAttributes["class"];
}
>();
</script>
<template>
<Primitive
data-sidebar="group-action"
:as="as"
:as-child="asChild"
:class="
cn(
'absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
// Increases the hit area of the button on mobile.
'after:absolute after:-inset-2 after:md:hidden',
'group-data-[collapsible=icon]:hidden',
props.class
)
"
>
<slot />
</Primitive>
</template>

View 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>
<div data-sidebar="group-content" :class="cn('w-full text-sm', props.class)">
<slot />
</div>
</template>

View File

@@ -0,0 +1,29 @@
<script setup lang="ts">
import type { PrimitiveProps } from "radix-vue";
import type { HTMLAttributes } from "vue";
import { Primitive } from "radix-vue";
import { cn } from "@/lib/utils";
const props = defineProps<
PrimitiveProps & {
class?: HTMLAttributes["class"];
}
>();
</script>
<template>
<Primitive
data-sidebar="group-label"
:as="as"
:as-child="asChild"
:class="
cn(
'duration-200 flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opa] ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
props.class
)
"
>
<slot />
</Primitive>
</template>

View 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>
<div data-sidebar="header" :class="cn('flex flex-col gap-2 p-2', props.class)">
<slot />
</div>
</template>

View File

@@ -0,0 +1,20 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue";
import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";
const props = defineProps<{
class?: HTMLAttributes["class"];
}>();
</script>
<template>
<Input
data-sidebar="input"
:class="
cn('h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring', props.class)
"
>
<slot />
</Input>
</template>

View File

@@ -0,0 +1,22 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue";
import { cn } from "@/lib/utils";
const props = defineProps<{
class?: HTMLAttributes["class"];
}>();
</script>
<template>
<main
:class="
cn(
'relative flex min-h-svh flex-1 flex-col bg-background',
'peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow',
props.class
)
"
>
<slot />
</main>
</template>

View 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>
<ul data-sidebar="menu" :class="cn('flex w-full min-w-0 flex-col gap-1', props.class)">
<slot />
</ul>
</template>

View File

@@ -0,0 +1,41 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue";
import { Primitive, type PrimitiveProps } from "radix-vue";
import { cn } from "@/lib/utils";
const props = withDefaults(
defineProps<
PrimitiveProps & {
showOnHover?: boolean;
class?: HTMLAttributes["class"];
}
>(),
{
as: "button",
}
);
</script>
<template>
<Primitive
data-sidebar="menu-action"
:class="
cn(
'absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0',
// Increases the hit area of the button on mobile.
'after:absolute after:-inset-2 after:md:hidden',
'peer-data-[size=sm]/menu-button:top-1',
'peer-data-[size=default]/menu-button:top-1.5',
'peer-data-[size=lg]/menu-button:top-2.5',
'group-data-[collapsible=icon]:hidden',
showOnHover &&
'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0',
props.class
)
"
:as="as"
:as-child="asChild"
>
<slot />
</Primitive>
</template>

View File

@@ -0,0 +1,27 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue";
import { cn } from "@/lib/utils";
const props = defineProps<{
class?: HTMLAttributes["class"];
}>();
</script>
<template>
<div
data-sidebar="menu-badge"
:class="
cn(
'absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground select-none pointer-events-none',
'peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground',
'peer-data-[size=sm]/menu-button:top-1',
'peer-data-[size=default]/menu-button:top-1.5',
'peer-data-[size=lg]/menu-button:top-2.5',
'group-data-[collapsible=icon]:hidden',
props.class
)
"
>
<slot />
</div>
</template>

View File

@@ -0,0 +1,54 @@
<script setup lang="ts">
import { type Component, computed } from "vue";
import SidebarMenuButtonChild, { type SidebarMenuButtonProps } from "./SidebarMenuButtonChild.vue";
import { useSidebar } from "./utils";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
defineOptions({
inheritAttrs: false,
});
const props = withDefaults(
defineProps<
SidebarMenuButtonProps & {
tooltip?: string | Component;
hotkey?: string;
}
>(),
{
as: "button",
variant: "default",
size: "default",
}
);
const { isMobile, state } = useSidebar();
const delegatedProps = computed(() => {
const { tooltip, hotkey, ...delegated } = props;
return delegated;
});
</script>
<template>
<SidebarMenuButtonChild v-if="!tooltip" v-bind="{ ...delegatedProps, ...$attrs }">
<slot />
</SidebarMenuButtonChild>
<Tooltip v-else>
<TooltipTrigger as-child>
<SidebarMenuButtonChild v-bind="{ ...delegatedProps, ...$attrs }" :size="state === 'collapsed' ? 'default' : 'lg'" :class="state === 'collapsed' ? '' : 'text-xl'">
<slot />
</SidebarMenuButtonChild>
</TooltipTrigger>
<TooltipContent side="right" align="center" :hidden="state !== 'collapsed' || isMobile">
<template v-if="typeof tooltip === 'string'">
{{ tooltip }}
</template>
<component :is="tooltip" v-else />
</TooltipContent>
<TooltipContent v-if="hotkey" :hidden="isMobile">
{{ hotkey }}
</TooltipContent>
</Tooltip>
</template>

View File

@@ -0,0 +1,33 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue";
import { Primitive, type PrimitiveProps } from "radix-vue";
import { type SidebarMenuButtonVariants, sidebarMenuButtonVariants } from ".";
import { cn } from "@/lib/utils";
export interface SidebarMenuButtonProps extends PrimitiveProps {
variant?: SidebarMenuButtonVariants["variant"];
size?: SidebarMenuButtonVariants["size"];
isActive?: boolean;
class?: HTMLAttributes["class"];
}
const props = withDefaults(defineProps<SidebarMenuButtonProps>(), {
as: "button",
variant: "default",
size: "default",
});
</script>
<template>
<Primitive
data-sidebar="menu-button"
:data-size="size"
:data-active="isActive"
:class="cn(sidebarMenuButtonVariants({ variant, size }), props.class)"
:as="as"
:as-child="asChild"
v-bind="$attrs"
>
<slot />
</Primitive>
</template>

View 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>
<li data-sidebar="menu-item" :class="cn('group/menu-item relative', props.class)">
<slot />
</li>
</template>

View File

@@ -0,0 +1,18 @@
<script setup lang="ts">
import { useSidebar } from "./utils";
import SidebarMenuButton from "./SidebarMenuButton.vue";
const { setOpenMobile } = useSidebar();
const props = defineProps<{
href: string;
}>();
</script>
<template>
<SidebarMenuButton as-child>
<NuxtLink :to="props.href" @click.prevent="setOpenMobile(false)">
<slot />
</NuxtLink>
</SidebarMenuButton>
</template>

View File

@@ -0,0 +1,26 @@
<script setup lang="ts">
import { computed, type HTMLAttributes } from "vue";
import { Skeleton } from "@/components/ui/skeleton";
import { cn } from "@/lib/utils";
const props = defineProps<{
showIcon?: boolean;
class?: HTMLAttributes["class"];
}>();
const width = computed(() => {
return `${Math.floor(Math.random() * 40) + 50}%`;
});
</script>
<template>
<div data-sidebar="menu-skeleton" :class="cn('rounded-md h-8 flex gap-2 px-2 items-center', props.class)">
<Skeleton v-if="showIcon" class="size-4 rounded-md" data-sidebar="menu-skeleton-icon" />
<Skeleton
class="h-4 max-w-[--skeleton-width] flex-1"
data-sidebar="menu-skeleton-text"
:style="{ '--skeleton-width': width }"
/>
</div>
</template>

View File

@@ -0,0 +1,23 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue";
import { cn } from "@/lib/utils";
const props = defineProps<{
class?: HTMLAttributes["class"];
}>();
</script>
<template>
<ul
data-sidebar="menu-badge"
:class="
cn(
'mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5',
'group-data-[collapsible=icon]:hidden',
props.class
)
"
>
<slot />
</ul>
</template>

View File

@@ -0,0 +1,42 @@
<script setup lang="ts">
import type { PrimitiveProps } from "radix-vue";
import type { HTMLAttributes } from "vue";
import { Primitive } from "radix-vue";
import { cn } from "@/lib/utils";
const props = withDefaults(
defineProps<
PrimitiveProps & {
size?: "sm" | "md";
isActive?: boolean;
class?: HTMLAttributes["class"];
}
>(),
{
as: "a",
size: "md",
}
);
</script>
<template>
<Primitive
data-sidebar="menu-sub-button"
:as="as"
:as-child="asChild"
:data-size="size"
:data-active="isActive"
:class="
cn(
'flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground',
'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
size === 'sm' && 'text-xs',
size === 'md' && 'text-sm',
'group-data-[collapsible=icon]:hidden',
props.class
)
"
>
<slot />
</Primitive>
</template>

View File

@@ -0,0 +1,7 @@
<script setup lang="ts"></script>
<template>
<li>
<slot />
</li>
</template>

View File

@@ -0,0 +1,90 @@
<script setup lang="ts">
import { useEventListener, useMediaQuery, useVModel } from "@vueuse/core";
import { TooltipProvider } from "radix-vue";
import { computed, type HTMLAttributes, type Ref, ref } from "vue";
import {
provideSidebarContext,
SIDEBAR_COOKIE_MAX_AGE,
SIDEBAR_COOKIE_NAME,
SIDEBAR_KEYBOARD_SHORTCUT,
SIDEBAR_WIDTH,
SIDEBAR_WIDTH_ICON,
} from "./utils";
import { cn } from "@/lib/utils";
const props = withDefaults(
defineProps<{
defaultOpen?: boolean;
open?: boolean;
class?: HTMLAttributes["class"];
}>(),
{
defaultOpen: true,
open: undefined,
}
);
const emits = defineEmits<{
"update:open": [open: boolean];
}>();
const isMobile = useMediaQuery("(max-width: 768px)");
const openMobile = ref(false);
const open = useVModel(props, "open", emits, {
defaultValue: props.defaultOpen ?? false,
passive: (props.open === undefined) as false,
}) as Ref<boolean>;
function setOpen(value: boolean) {
open.value = value; // emits('update:open', value)
// This sets the cookie to keep the sidebar state.
document.cookie = `${SIDEBAR_COOKIE_NAME}=${open.value}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
}
function setOpenMobile(value: boolean) {
openMobile.value = value;
}
// Helper to toggle the sidebar.
function toggleSidebar() {
return isMobile.value ? setOpenMobile(!openMobile.value) : setOpen(!open.value);
}
useEventListener("keydown", (event: KeyboardEvent) => {
if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
event.preventDefault();
toggleSidebar();
}
});
// We add a state so that we can do data-state="expanded" or "collapsed".
// This makes it easier to style the sidebar with Tailwind classes.
const state = computed(() => (open.value ? "expanded" : "collapsed"));
provideSidebarContext({
state,
open,
setOpen,
isMobile,
openMobile,
setOpenMobile,
toggleSidebar,
});
</script>
<template>
<TooltipProvider :delay-duration="0">
<div
:style="{
'--sidebar-width': SIDEBAR_WIDTH,
'--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
}"
:class="cn('group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar', props.class)"
v-bind="$attrs"
>
<slot />
</div>
</TooltipProvider>
</template>

View File

@@ -0,0 +1,34 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue";
import { useSidebar } from "./utils";
import { cn } from "@/lib/utils";
const props = defineProps<{
class?: HTMLAttributes["class"];
}>();
const { toggleSidebar } = useSidebar();
</script>
<template>
<button
data-sidebar="rail"
aria-label="Toggle Sidebar"
:tabindex="-1"
title="Toggle Sidebar"
:class="
cn(
'absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex',
'[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize',
'[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',
'group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar',
'[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',
'[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',
props.class
)
"
@click="toggleSidebar"
>
<slot />
</button>
</template>

View File

@@ -0,0 +1,15 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue";
import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";
const props = defineProps<{
class?: HTMLAttributes["class"];
}>();
</script>
<template>
<Separator data-sidebar="separator" :class="cn('mx-2 w-auto bg-sidebar-border', props.class)">
<slot />
</Separator>
</template>

View File

@@ -0,0 +1,21 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue";
import { useSidebar } from "./utils";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import MdiMenu from "~icons/mdi/menu";
const props = defineProps<{
class?: HTMLAttributes["class"];
variant?: "ghost" | "default";
}>();
const { toggleSidebar } = useSidebar();
</script>
<template>
<Button data-sidebar="trigger" :variant="props.variant ?? 'ghost'" size="icon" :class="cn('size-9 [&_svg]:size-6', props.class)" @click="toggleSidebar">
<MdiMenu class="text-primary-foreground" />
<span class="sr-only">Toggle Sidebar</span>
</Button>
</template>

View File

@@ -0,0 +1,60 @@
import type { HTMLAttributes } from "vue";
import { cva, type VariantProps } from "class-variance-authority";
export interface SidebarProps {
side?: "left" | "right";
variant?: "sidebar" | "floating" | "inset";
collapsible?: "offcanvas" | "icon" | "none";
class?: HTMLAttributes["class"];
}
export { default as Sidebar } from "./Sidebar.vue";
export { default as SidebarContent } from "./SidebarContent.vue";
export { default as SidebarFooter } from "./SidebarFooter.vue";
export { default as SidebarGroup } from "./SidebarGroup.vue";
export { default as SidebarGroupAction } from "./SidebarGroupAction.vue";
export { default as SidebarGroupContent } from "./SidebarGroupContent.vue";
export { default as SidebarGroupLabel } from "./SidebarGroupLabel.vue";
export { default as SidebarHeader } from "./SidebarHeader.vue";
export { default as SidebarInput } from "./SidebarInput.vue";
export { default as SidebarInset } from "./SidebarInset.vue";
export { default as SidebarMenu } from "./SidebarMenu.vue";
export { default as SidebarMenuAction } from "./SidebarMenuAction.vue";
export { default as SidebarMenuBadge } from "./SidebarMenuBadge.vue";
export { default as SidebarMenuButton } from "./SidebarMenuButton.vue";
export { default as SidebarMenuLink } from "./SidebarMenuLink.vue";
export { default as SidebarMenuItem } from "./SidebarMenuItem.vue";
export { default as SidebarMenuSkeleton } from "./SidebarMenuSkeleton.vue";
export { default as SidebarMenuSub } from "./SidebarMenuSub.vue";
export { default as SidebarMenuSubButton } from "./SidebarMenuSubButton.vue";
export { default as SidebarMenuSubItem } from "./SidebarMenuSubItem.vue";
export { default as SidebarProvider } from "./SidebarProvider.vue";
export { default as SidebarRail } from "./SidebarRail.vue";
export { default as SidebarSeparator } from "./SidebarSeparator.vue";
export { default as SidebarTrigger } from "./SidebarTrigger.vue";
export { useSidebar } from "./utils";
export const sidebarMenuButtonVariants = cva(
"peer/menu-button ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none transition-[width,height,padding] focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:font-medium group-data-[collapsible=icon]:!size-10 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-6 [&>svg]:shrink-0",
{
variants: {
variant: {
default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
outline:
"hover:bg-sidebar-accent hover:text-sidebar-accent-foreground bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
},
size: {
default: "h-8 text-sm",
sm: "h-7 text-xs",
lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);
export type SidebarMenuButtonVariants = VariantProps<typeof sidebarMenuButtonVariants>;

View File

@@ -0,0 +1,19 @@
import type { ComputedRef, Ref } from "vue";
import { createContext } from "radix-vue";
export const SIDEBAR_COOKIE_NAME = "sidebar:state";
export const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
export const SIDEBAR_WIDTH = "16rem";
export const SIDEBAR_WIDTH_MOBILE = "18rem";
export const SIDEBAR_WIDTH_ICON = "3.5rem";
export const SIDEBAR_KEYBOARD_SHORTCUT = "b";
export const [useSidebar, provideSidebarContext] = createContext<{
state: ComputedRef<"expanded" | "collapsed">;
open: Ref<boolean>;
setOpen: (value: boolean) => void;
isMobile: Ref<boolean>;
openMobile: Ref<boolean>;
setOpenMobile: (value: boolean) => void;
toggleSidebar: () => void;
}>("Sidebar");

View File

@@ -0,0 +1,14 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue";
import { cn } from "@/lib/utils";
interface SkeletonProps {
class?: HTMLAttributes["class"];
}
const props = defineProps<SkeletonProps>();
</script>
<template>
<div :class="cn('animate-pulse rounded-md bg-muted', props.class)" />
</template>

View File

@@ -0,0 +1 @@
export { default as Skeleton } from "./Skeleton.vue";

View File

@@ -0,0 +1,14 @@
<script setup lang="ts">
import { TooltipRoot, type TooltipRootEmits, type TooltipRootProps, useForwardPropsEmits } from "radix-vue";
const props = defineProps<TooltipRootProps>();
const emits = defineEmits<TooltipRootEmits>();
const forwarded = useForwardPropsEmits(props, emits);
</script>
<template>
<TooltipRoot v-bind="forwarded">
<slot />
</TooltipRoot>
</template>

View File

@@ -0,0 +1,45 @@
<script setup lang="ts">
import {
TooltipContent,
type TooltipContentEmits,
type TooltipContentProps,
TooltipPortal,
useForwardPropsEmits,
} from "radix-vue";
import { computed, type HTMLAttributes } from "vue";
import { cn } from "@/lib/utils";
defineOptions({
inheritAttrs: false,
});
const props = withDefaults(defineProps<TooltipContentProps & { class?: HTMLAttributes["class"] }>(), {
sideOffset: 4,
});
const emits = defineEmits<TooltipContentEmits>();
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
const forwarded = useForwardPropsEmits(delegatedProps, emits);
</script>
<template>
<TooltipPortal>
<TooltipContent
v-bind="{ ...forwarded, ...$attrs }"
:class="
cn(
'z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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
)
"
>
<slot />
</TooltipContent>
</TooltipPortal>
</template>

View File

@@ -0,0 +1,11 @@
<script setup lang="ts">
import { TooltipProvider, type TooltipProviderProps } from "radix-vue";
const props = defineProps<TooltipProviderProps>();
</script>
<template>
<TooltipProvider v-bind="props">
<slot />
</TooltipProvider>
</template>

View File

@@ -0,0 +1,11 @@
<script setup lang="ts">
import { TooltipTrigger, type TooltipTriggerProps } from "radix-vue";
const props = defineProps<TooltipTriggerProps>();
</script>
<template>
<TooltipTrigger v-bind="props">
<slot />
</TooltipTrigger>
</template>

View File

@@ -0,0 +1,4 @@
export { default as Tooltip } from "./Tooltip.vue";
export { default as TooltipContent } from "./TooltipContent.vue";
export { default as TooltipProvider } from "./TooltipProvider.vue";
export { default as TooltipTrigger } from "./TooltipTrigger.vue";

View File

@@ -17,6 +17,9 @@ export function useTheme(): UseTheme {
if (htmlEl) { if (htmlEl) {
htmlEl.value?.setAttribute("data-theme", newTheme); htmlEl.value?.setAttribute("data-theme", newTheme);
// FIXME: this is a hack to remove the theme class from the html element
htmlEl.value?.classList.remove(...themes);
htmlEl.value?.classList.add("theme-" + newTheme);
} }
themeRef.value = newTheme; themeRef.value = newTheme;
@@ -62,3 +65,39 @@ export function useIsDark() {
return darkthemes.includes(theme.theme.value); return darkthemes.includes(theme.theme.value);
}); });
} }
export const themes = [
"dark",
"theme-aqua",
"theme-black",
"theme-bumblebee",
"theme-cmyk",
"theme-corporate",
"theme-cupcake",
"theme-cyberpunk",
"theme-dark",
"theme-dracula",
"theme-emerald",
"theme-fantasy",
"theme-forest",
"theme-garden",
"theme-halloween",
"theme-light",
"theme-lofi",
"theme-luxury",
"theme-pastel",
"theme-retro",
"theme-synthwave",
"theme-valentine",
"theme-wireframe",
"theme-autumn",
"theme-business",
"theme-acid",
"theme-lemonade",
"theme-night",
"theme-coffee",
"theme-winter",
"theme-dim",
"theme-nord",
"theme-sunset",
];

View File

@@ -12,104 +12,112 @@
<LocationCreateModal v-model="modals.location" /> <LocationCreateModal v-model="modals.location" />
<QuickMenuModal v-model="modals.quickMenu" :actions="quickMenuActions" /> <QuickMenuModal v-model="modals.quickMenu" :actions="quickMenuActions" />
<AppToast /> <AppToast />
<div class="drawer drawer-mobile"> <SidebarProvider>
<input id="my-drawer-2" v-model="drawerToggle" type="checkbox" class="drawer-toggle" /> <Sidebar collapsible="icon">
<div class="drawer-content justify-center bg-base-300 pt-20 lg:pt-0"> <SidebarHeader class="items-center bg-base-200">
<AppHeaderDecor v-if="preferences.displayHeaderDecor" class="-mt-10 hidden lg:block" /> <SidebarGroupLabel class="text-base">{{ $t("global.welcome", { username: username }) }}</SidebarGroupLabel>
<!-- Button --> <NuxtLink class="avatar placeholder group-data-[collapsible=icon]:hidden" to="/home">
<div class="navbar drawer-button fixed top-0 z-[99] bg-primary shadow-md lg:hidden"> <div class="w-24 rounded-full bg-base-300 p-4 text-neutral-content">
<label for="my-drawer-2" class="btn btn-square btn-ghost drawer-button text-base-100 lg:hidden"> <AppLogo />
<MdiMenu class="size-6" /> </div>
</label>
<NuxtLink to="/home">
<h2 class="flex text-3xl font-bold tracking-tight text-base-100">
HomeB
<AppLogo class="-mb-3 w-8" />
x
</h2>
</NuxtLink> </NuxtLink>
</div> <DropdownMenu>
<DropdownMenuTrigger as-child>
<SidebarMenuButton
class="flex justify-center bg-primary text-primary-foreground shadow hover:bg-primary/90 group-data-[collapsible=icon]:justify-start"
:tooltip="$t('global.create')"
hotkey="Shortcut: Ctrl+`"
>
<MdiPlus />
<span>
{{ $t("global.create") }}
</span>
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent class="min-w-[var(--radix-dropdown-menu-trigger-width)]">
<DropdownMenuItem
v-for="btn in dropdown"
:key="btn.id"
class="group cursor-pointer text-lg"
@click="btn.action"
>
{{ btn.name.value }}
<kbd v-if="btn.shortcut" class="kbd kbd-sm ml-auto hidden text-primary group-hover:inline">{{
btn.shortcut.replaceAll("Shift+", "⇧")
}}</kbd>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarHeader>
<slot></slot> <SidebarContent class="bg-base-200">
<footer v-if="status" class="bottom-0 w-full bg-base-300 pb-4 text-center text-secondary-content"> <SidebarGroup>
<p class="text-center text-sm"> <SidebarMenu>
<a href="https://github.com/sysadminsmedia/homebox/releases/tag/{{ status.build.version }}" target="_blank"> <SidebarMenuItem v-for="n in nav" :key="n.id">
{{ $t("global.version", { version: status.build.version }) }} ~ <SidebarMenuLink
{{ $t("global.build", { build: status.build.commit }) }}</a :href="n.to"
> :class="{
~ 'bg-secondary text-secondary-foreground': n.active?.value,
<a href="https://homebox.software/en/api.html" target="_blank">API</a> 'text-nowrap': typeof locale === 'string' && locale.startsWith('zh-'),
</p> 'hover:bg-base-300': !n.active?.value,
</footer> }"
</div> :tooltip="n.name.value"
>
<component :is="n.icon" />
<span>{{ n.name.value }}</span>
</SidebarMenuLink>
</SidebarMenuItem>
</SidebarMenu></SidebarGroup
>
</SidebarContent>
<!-- Sidebar --> <SidebarFooter class="bg-base-200">
<div class="drawer-side shadow-lg"> <SidebarMenuButton
<label for="my-drawer-2" class="drawer-overlay"></label> class="flex justify-center hover:bg-base-300 group-data-[collapsible=icon]:justify-start group-data-[collapsible=icon]:bg-destructive group-data-[collapsible=icon]:text-destructive-foreground group-data-[collapsible=icon]:shadow-sm group-data-[collapsible=icon]:hover:bg-destructive/90"
:tooltip="$t('global.sign_out')"
<!-- Top Section --> @click="logout"
<div class="flex min-w-40 max-w-min flex-col bg-base-200 p-5 md:py-10"> >
<div class="space-y-8"> <MdiLogout />
<div class="flex flex-col items-center gap-4"> <span>
<p>{{ $t("global.welcome", { username: username }) }}</p>
<NuxtLink class="avatar placeholder" to="/home">
<div class="w-24 rounded-full bg-base-300 p-4 text-neutral-content">
<AppLogo />
</div>
</NuxtLink>
</div>
<div class="flex flex-col bg-base-200">
<div class="mb-6">
<div class="dropdown tooltip visible w-full" data-tip="Shortcut: Ctrl+`">
<label tabindex="0" class="text-no-transform btn btn-primary btn-block text-lg">
<span>
<MdiPlus class="-ml-1 mr-1" />
</span>
{{ $t("global.create") }}
</label>
<ul tabindex="0" class="dropdown-content menu rounded-box w-full bg-base-100 p-2 shadow">
<li v-for="btn in dropdown" :key="btn.id">
<button class="group" @click="btn.action">
{{ btn.name.value }}
<kbd
v-if="btn.shortcut"
class="kbd kbd-sm ml-auto hidden text-neutral-400 group-hover:inline"
>{{ btn.shortcut.replaceAll("Shift+", "⇧") }}</kbd
>
</button>
</li>
</ul>
</div>
</div>
<ul class="menu mx-auto flex flex-col gap-2">
<li v-for="n in nav" :key="n.id" class="text-xl" @click="unfocus">
<NuxtLink
v-if="n.to"
class="rounded-btn"
:to="n.to"
:class="{
'bg-secondary text-secondary-content': n.active?.value,
'text-nowrap': typeof locale === 'string' && locale.startsWith('zh-'),
}"
>
<component :is="n.icon" class="mr-4 size-6" />
{{ n.name.value }}
</NuxtLink>
</li>
</ul>
</div>
</div>
<!-- Bottom -->
<div class="mx-2 mt-auto flex flex-col">
<button class="rounded-btn p-3 transition-colors hover:bg-base-300" @click="logout">
{{ $t("global.sign_out") }} {{ $t("global.sign_out") }}
</button> </span>
</SidebarMenuButton>
</SidebarFooter>
<SidebarRail />
</Sidebar>
<SidebarInset class="min-h-screen bg-base-300">
<div class="justify-center pt-20 lg:pt-0">
<AppHeaderDecor v-if="preferences.displayHeaderDecor" class="-mt-10 hidden lg:block" />
<SidebarTrigger class="absolute left-2 top-2 hidden lg:flex" variant="default" />
<div class="fixed top-0 z-20 flex h-16 w-full items-center gap-2 bg-primary p-2 shadow-md lg:hidden">
<SidebarTrigger />
<NuxtLink to="/home">
<h2 class="flex text-3xl font-bold tracking-tight text-base-100">
HomeB
<AppLogo class="-mb-3 w-8" />
x
</h2>
</NuxtLink>
</div> </div>
<slot></slot>
<footer v-if="status" class="bottom-0 w-full bg-base-300 pb-4 text-center text-secondary-content">
<p class="text-center text-sm">
<a
href="https://github.com/sysadminsmedia/homebox/releases/tag/{{ status.build.version }}"
target="_blank"
>
{{ $t("global.version", { version: status.build.version }) }} ~
{{ $t("global.build", { build: status.build.commit }) }}</a
>
~
<a href="https://homebox.software/en/api.html" target="_blank">API</a>
</p>
</footer>
</div> </div>
</div> </SidebarInset>
</div> </SidebarProvider>
</div> </div>
</template> </template>
@@ -125,8 +133,30 @@
import MdiAccount from "~icons/mdi/account"; import MdiAccount from "~icons/mdi/account";
import MdiCog from "~icons/mdi/cog"; import MdiCog from "~icons/mdi/cog";
import MdiWrench from "~icons/mdi/wrench"; import MdiWrench from "~icons/mdi/wrench";
import MdiMenu from "~icons/mdi/menu";
import MdiPlus from "~icons/mdi/plus"; import MdiPlus from "~icons/mdi/plus";
import MdiLogout from "~icons/mdi/logout";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarHeader,
SidebarInset,
SidebarRail,
SidebarTrigger,
SidebarGroup,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuItem,
SidebarMenuButton,
SidebarMenuLink,
} from "@/components/ui/sidebar";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
const { t, locale } = useI18n(); const { t, locale } = useI18n();
const username = computed(() => authCtx.user?.name || "User"); const username = computed(() => authCtx.user?.name || "User");
@@ -218,13 +248,6 @@
const route = useRoute(); const route = useRoute();
const drawerToggle = ref();
function unfocus() {
// unfocus current element
drawerToggle.value = false;
}
const nav = [ const nav = [
{ {
icon: MdiHome, icon: MdiHome,

6
frontend/lib/utils.ts Normal file
View File

@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

View File

@@ -8,7 +8,14 @@ export default defineNuxtConfig({
transpile: ["vue-i18n"], transpile: ["vue-i18n"],
}, },
modules: ["@nuxtjs/tailwindcss", "@pinia/nuxt", "@vueuse/nuxt", "@vite-pwa/nuxt", "unplugin-icons/nuxt"], modules: [
"@nuxtjs/tailwindcss",
"@pinia/nuxt",
"@vueuse/nuxt",
"@vite-pwa/nuxt",
"unplugin-icons/nuxt",
"shadcn-nuxt",
],
nitro: { nitro: {
devProxy: { devProxy: {

View File

@@ -5,9 +5,9 @@
"dev": "nuxt dev", "dev": "nuxt dev",
"preview": "nuxt preview", "preview": "nuxt preview",
"postinstall": "nuxt prepare", "postinstall": "nuxt prepare",
"lint": "eslint --ext \".ts,.js,.vue\" --ignore-path ../.gitignore .", "lint": "eslint --ext \".ts,.js,.vue\" --ignore-path ../.gitignore --ignore-pattern \"components/ui\" .",
"lint:fix": "eslint --ext \".ts,.js,.vue\" --ignore-path ../.gitignore . --fix", "lint:fix": "eslint --ext \".ts,.js,.vue\" --ignore-path ../.gitignore --ignore-pattern \"components/ui\" . --fix",
"lint:ci": "eslint --ext \".ts,.js,.vue\" --ignore-path ../.gitignore . --max-warnings 1", "lint:ci": "eslint --ext \".ts,.js,.vue\" --ignore-path ../.gitignore --ignore-pattern \"components/ui\" . --max-warnings 1",
"typecheck": "pnpm vue-tsc --noEmit", "typecheck": "pnpm vue-tsc --noEmit",
"test:ci": "TEST_SHUTDOWN_API_SERVER=true vitest --run --config ./test/vitest.config.ts", "test:ci": "TEST_SHUTDOWN_API_SERVER=true vitest --run --config ./test/vitest.config.ts",
"test:local": "TEST_SHUTDOWN_API_SERVER=false && vitest --run --config ./test/vitest.config.ts", "test:local": "TEST_SHUTDOWN_API_SERVER=false && vitest --run --config ./test/vitest.config.ts",
@@ -33,6 +33,7 @@
"isomorphic-fetch": "^3.0.0", "isomorphic-fetch": "^3.0.0",
"nuxt": "3.12.4", "nuxt": "3.12.4",
"prettier": "^3.4.2", "prettier": "^3.4.2",
"shadcn-nuxt": "0.11.3",
"typescript": "5.6.2", "typescript": "5.6.2",
"unplugin-icons": "^0.18.5", "unplugin-icons": "^0.18.5",
"vite-plugin-eslint": "^1.8.1", "vite-plugin-eslint": "^1.8.1",
@@ -42,6 +43,7 @@
}, },
"dependencies": { "dependencies": {
"@headlessui/vue": "^1.7.23", "@headlessui/vue": "^1.7.23",
"@nuxtjs/color-mode": "^3.5.2",
"@nuxtjs/tailwindcss": "^6.12.2", "@nuxtjs/tailwindcss": "^6.12.2",
"@pinia/nuxt": "^0.5.5", "@pinia/nuxt": "^0.5.5",
"@tailwindcss/aspect-ratio": "^0.4.2", "@tailwindcss/aspect-ratio": "^0.4.2",
@@ -49,20 +51,27 @@
"@tailwindcss/typography": "^0.5.15", "@tailwindcss/typography": "^0.5.15",
"@types/lunr": "^2.3.7", "@types/lunr": "^2.3.7",
"@vuepic/vue-datepicker": "^8.8.1", "@vuepic/vue-datepicker": "^8.8.1",
"@vueuse/core": "^12.5.0",
"@vueuse/nuxt": "^10.11.1", "@vueuse/nuxt": "^10.11.1",
"@vueuse/router": "^10.11.1", "@vueuse/router": "^10.11.1",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"daisyui": "^2.52.0", "daisyui": "^2.52.0",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"dompurify": "^3.2.3", "dompurify": "^3.2.3",
"h3": "^1.13.0", "h3": "^1.13.0",
"http-proxy": "^1.18.1", "http-proxy": "^1.18.1",
"lucide-vue-next": "^0.474.0",
"lunr": "^2.3.9", "lunr": "^2.3.9",
"markdown-it": "^14.1.0", "markdown-it": "^14.1.0",
"pinia": "^2.3.0", "pinia": "^2.3.0",
"postcss": "^8.4.49", "postcss": "^8.4.49",
"radix-vue": "^1.9.12",
"semver": "^7.6.3", "semver": "^7.6.3",
"tailwind-merge": "^2.6.0",
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7",
"vue": "3.4.8", "vue": "3.4.8",
"vue-router": "^4.5.0" "vue-router": "^4.5.0"
} }

View File

@@ -478,10 +478,11 @@
{{ $t("profile.display_header", { currentValue: preferences.displayHeaderDecor }) }} {{ $t("profile.display_header", { currentValue: preferences.displayHeaderDecor }) }}
</BaseButton> </BaseButton>
</div> </div>
<div class="rounded-box grid grid-cols-1 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5"> <div class="homebox rounded-box grid grid-cols-1 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5">
<div <div
v-for="theme in themes" v-for="theme in themes"
:key="theme.value" :key="theme.value"
:class="'theme-' + theme.value"
class="overflow-hidden rounded-lg border border-base-content/20 outline-2 outline-offset-2 outline-base-content hover:border-base-content/40" class="overflow-hidden rounded-lg border border-base-content/20 outline-2 outline-offset-2 outline-base-content hover:border-base-content/40"
:data-theme="theme.value" :data-theme="theme.value"
:data-set-theme="theme.value" :data-set-theme="theme.value"

View File

@@ -0,0 +1,5 @@
<script setup lang="ts"></script>
<template>
<div>testing</div>
</template>

229
frontend/pnpm-lock.yaml generated
View File

@@ -11,6 +11,9 @@ importers:
'@headlessui/vue': '@headlessui/vue':
specifier: ^1.7.23 specifier: ^1.7.23
version: 1.7.23(vue@3.4.8(typescript@5.6.2)) version: 1.7.23(vue@3.4.8(typescript@5.6.2))
'@nuxtjs/color-mode':
specifier: ^3.5.2
version: 3.5.2(magicast@0.3.5)(rollup@4.29.1)
'@nuxtjs/tailwindcss': '@nuxtjs/tailwindcss':
specifier: ^6.12.2 specifier: ^6.12.2
version: 6.12.2(magicast@0.3.5)(rollup@4.29.1) version: 6.12.2(magicast@0.3.5)(rollup@4.29.1)
@@ -32,6 +35,9 @@ importers:
'@vuepic/vue-datepicker': '@vuepic/vue-datepicker':
specifier: ^8.8.1 specifier: ^8.8.1
version: 8.8.1(vue@3.4.8(typescript@5.6.2)) version: 8.8.1(vue@3.4.8(typescript@5.6.2))
'@vueuse/core':
specifier: ^12.5.0
version: 12.5.0(typescript@5.6.2)
'@vueuse/nuxt': '@vueuse/nuxt':
specifier: ^10.11.1 specifier: ^10.11.1
version: 10.11.1(magicast@0.3.5)(nuxt@3.12.4(@parcel/watcher@2.5.0)(@types/node@22.10.5)(db0@0.2.1)(eslint@8.57.1)(ioredis@5.4.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.29.1)(terser@5.37.0)(typescript@5.6.2)(vite@5.4.11(@types/node@22.10.5)(terser@5.37.0))(vue-tsc@2.1.6(typescript@5.6.2)))(rollup@4.29.1)(vue@3.4.8(typescript@5.6.2)) version: 10.11.1(magicast@0.3.5)(nuxt@3.12.4(@parcel/watcher@2.5.0)(@types/node@22.10.5)(db0@0.2.1)(eslint@8.57.1)(ioredis@5.4.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.29.1)(terser@5.37.0)(typescript@5.6.2)(vite@5.4.11(@types/node@22.10.5)(terser@5.37.0))(vue-tsc@2.1.6(typescript@5.6.2)))(rollup@4.29.1)(vue@3.4.8(typescript@5.6.2))
@@ -41,6 +47,12 @@ importers:
autoprefixer: autoprefixer:
specifier: ^10.4.20 specifier: ^10.4.20
version: 10.4.20(postcss@8.4.49) version: 10.4.20(postcss@8.4.49)
class-variance-authority:
specifier: ^0.7.1
version: 0.7.1
clsx:
specifier: ^2.1.1
version: 2.1.1
daisyui: daisyui:
specifier: ^2.52.0 specifier: ^2.52.0
version: 2.52.0(autoprefixer@10.4.20(postcss@8.4.49))(postcss@8.4.49) version: 2.52.0(autoprefixer@10.4.20(postcss@8.4.49))(postcss@8.4.49)
@@ -56,6 +68,9 @@ importers:
http-proxy: http-proxy:
specifier: ^1.18.1 specifier: ^1.18.1
version: 1.18.1 version: 1.18.1
lucide-vue-next:
specifier: ^0.474.0
version: 0.474.0(vue@3.4.8(typescript@5.6.2))
lunr: lunr:
specifier: ^2.3.9 specifier: ^2.3.9
version: 2.3.9 version: 2.3.9
@@ -68,12 +83,21 @@ importers:
postcss: postcss:
specifier: ^8.4.49 specifier: ^8.4.49
version: 8.4.49 version: 8.4.49
radix-vue:
specifier: ^1.9.12
version: 1.9.12(vue@3.4.8(typescript@5.6.2))
semver: semver:
specifier: ^7.6.3 specifier: ^7.6.3
version: 7.6.3 version: 7.6.3
tailwind-merge:
specifier: ^2.6.0
version: 2.6.0
tailwindcss: tailwindcss:
specifier: ^3.4.17 specifier: ^3.4.17
version: 3.4.17 version: 3.4.17
tailwindcss-animate:
specifier: ^1.0.7
version: 1.0.7(tailwindcss@3.4.17)
vue: vue:
specifier: 3.4.8 specifier: 3.4.8
version: 3.4.8(typescript@5.6.2) version: 3.4.8(typescript@5.6.2)
@@ -135,6 +159,9 @@ importers:
prettier: prettier:
specifier: ^3.4.2 specifier: ^3.4.2
version: 3.4.2 version: 3.4.2
shadcn-nuxt:
specifier: 0.11.3
version: 0.11.3(magicast@0.3.5)(rollup@4.29.1)
typescript: typescript:
specifier: 5.6.2 specifier: 5.6.2
version: 5.6.2 version: 5.6.2
@@ -1173,6 +1200,18 @@ packages:
resolution: {integrity: sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==} resolution: {integrity: sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0, npm: '>=6.14.13'} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0, npm: '>=6.14.13'}
'@floating-ui/core@1.6.9':
resolution: {integrity: sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==}
'@floating-ui/dom@1.6.13':
resolution: {integrity: sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==}
'@floating-ui/utils@0.2.9':
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
'@floating-ui/vue@1.1.6':
resolution: {integrity: sha512-XFlUzGHGv12zbgHNk5FN2mUB7ROul3oG2ENdTpWdE+qMFxyNxWSRmsoyhiEnpmabNm6WnUvR1OvJfUfN4ojC1A==}
'@formatjs/ecma402-abstract@2.3.2': '@formatjs/ecma402-abstract@2.3.2':
resolution: {integrity: sha512-6sE5nyvDloULiyOMbOTJEEgWL32w+VHkZQs8S02Lnn8Y/O5aQhjOEXwWzvR7SsBE/exxlSpY2EsWZgqHbtLatg==} resolution: {integrity: sha512-6sE5nyvDloULiyOMbOTJEEgWL32w+VHkZQs8S02Lnn8Y/O5aQhjOEXwWzvR7SsBE/exxlSpY2EsWZgqHbtLatg==}
@@ -1216,6 +1255,12 @@ packages:
'@iconify/utils@2.2.1': '@iconify/utils@2.2.1':
resolution: {integrity: sha512-0/7J7hk4PqXmxo5PDBDxmnecw5PxklZJfNjIVG9FM0mEfVrvfudS22rYWsqVk6gR3UJ/mSYS90X4R3znXnqfNA==} resolution: {integrity: sha512-0/7J7hk4PqXmxo5PDBDxmnecw5PxklZJfNjIVG9FM0mEfVrvfudS22rYWsqVk6gR3UJ/mSYS90X4R3znXnqfNA==}
'@internationalized/date@3.7.0':
resolution: {integrity: sha512-VJ5WS3fcVx0bejE/YHfbDKR/yawZgKqn/if+oEeLqNwBtPzVB06olkfcnojTmEMX+gTpH+FlQ69SHNitJ8/erQ==}
'@internationalized/number@3.6.0':
resolution: {integrity: sha512-PtrRcJVy7nw++wn4W2OuePQQfTqDzfusSuY1QTtui4wa7r+rGVtR75pO8CyKvHvzyQYi3Q1uO5sY0AsB4e65Bw==}
'@intlify/bundle-utils@8.0.0': '@intlify/bundle-utils@8.0.0':
resolution: {integrity: sha512-1B++zykRnMwQ+20SpsZI1JCnV/YJt9Oq7AGlEurzkWJOFtFAVqaGc/oV36PBRYeiKnTbY9VYfjBimr2Vt42wLQ==} resolution: {integrity: sha512-1B++zykRnMwQ+20SpsZI1JCnV/YJt9Oq7AGlEurzkWJOFtFAVqaGc/oV36PBRYeiKnTbY9VYfjBimr2Vt42wLQ==}
engines: {node: '>= 14.16'} engines: {node: '>= 14.16'}
@@ -1383,6 +1428,9 @@ packages:
peerDependencies: peerDependencies:
vue: ^3.3.4 vue: ^3.3.4
'@nuxtjs/color-mode@3.5.2':
resolution: {integrity: sha512-cC6RfgZh3guHBMLLjrBB2Uti5eUoGM9KyauOaYS9ETmxNWBMTvpgjvSiSJp1OFljIXPIqVTJ3xtJpSNZiO3ZaA==}
'@nuxtjs/eslint-config-typescript@12.1.0': '@nuxtjs/eslint-config-typescript@12.1.0':
resolution: {integrity: sha512-l2fLouDYwdAvCZEEw7wGxOBj+i8TQcHFu3zMPTLqKuv1qu6WcZIr0uztkbaa8ND1uKZ9YPqKx6UlSOjM4Le69Q==} resolution: {integrity: sha512-l2fLouDYwdAvCZEEw7wGxOBj+i8TQcHFu3zMPTLqKuv1qu6WcZIr0uztkbaa8ND1uKZ9YPqKx6UlSOjM4Le69Q==}
peerDependencies: peerDependencies:
@@ -1396,6 +1444,9 @@ packages:
'@nuxtjs/tailwindcss@6.12.2': '@nuxtjs/tailwindcss@6.12.2':
resolution: {integrity: sha512-qPJiFH67CkTj/2kBGBzqXihOD1rQXMsbVS4vdQvfBxOBLPfGhU1yw7AATdhPl2BBjO2krjJLuZj39t7dnDYOwg==} resolution: {integrity: sha512-qPJiFH67CkTj/2kBGBzqXihOD1rQXMsbVS4vdQvfBxOBLPfGhU1yw7AATdhPl2BBjO2krjJLuZj39t7dnDYOwg==}
'@oxc-parser/wasm@0.29.0':
resolution: {integrity: sha512-Ks5yFtJHypJZUdSNLImwtfkDt0/8ll9CDPyfmldhudtKB/1o6F/WQGWA4Oo+bCskDIp2MPKc3HfHccN3ALhtSg==}
'@parcel/watcher-android-arm64@2.5.0': '@parcel/watcher-android-arm64@2.5.0':
resolution: {integrity: sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==} resolution: {integrity: sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
@@ -1723,6 +1774,9 @@ packages:
'@surma/rollup-plugin-off-main-thread@2.2.3': '@surma/rollup-plugin-off-main-thread@2.2.3':
resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==} resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==}
'@swc/helpers@0.5.15':
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
'@tailwindcss/aspect-ratio@0.4.2': '@tailwindcss/aspect-ratio@0.4.2':
resolution: {integrity: sha512-8QPrypskfBa7QIMuKHg2TA7BqES6vhBrDLOv8Unb6FcFyd3TjKbc6lcmb9UPQHxfl24sXoJ41ux/H7qQQvfaSQ==} resolution: {integrity: sha512-8QPrypskfBa7QIMuKHg2TA7BqES6vhBrDLOv8Unb6FcFyd3TjKbc6lcmb9UPQHxfl24sXoJ41ux/H7qQQvfaSQ==}
peerDependencies: peerDependencies:
@@ -2041,9 +2095,15 @@ packages:
'@vueuse/core@10.11.1': '@vueuse/core@10.11.1':
resolution: {integrity: sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==} resolution: {integrity: sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==}
'@vueuse/core@12.5.0':
resolution: {integrity: sha512-GVyH1iYqNANwcahAx8JBm6awaNgvR/SwZ1fjr10b8l1HIgDp82ngNbfzJUgOgWEoxjL+URAggnlilAEXwCOZtg==}
'@vueuse/metadata@10.11.1': '@vueuse/metadata@10.11.1':
resolution: {integrity: sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==} resolution: {integrity: sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==}
'@vueuse/metadata@12.5.0':
resolution: {integrity: sha512-Ui7Lo2a7AxrMAXRF+fAp9QsXuwTeeZ8fIB9wsLHqzq9MQk+2gMYE2IGJW48VMJ8ecvCB3z3GsGLKLbSasQ5Qlg==}
'@vueuse/nuxt@10.11.1': '@vueuse/nuxt@10.11.1':
resolution: {integrity: sha512-UiaYSIwOkmUVn8Gl1AqtLWYR12flO+8sEu9X0Y1fNjSR7EWy9jMuiCvOGqwtoeTsqfHrivl0d5HfMzr11GFnMA==} resolution: {integrity: sha512-UiaYSIwOkmUVn8Gl1AqtLWYR12flO+8sEu9X0Y1fNjSR7EWy9jMuiCvOGqwtoeTsqfHrivl0d5HfMzr11GFnMA==}
peerDependencies: peerDependencies:
@@ -2057,6 +2117,9 @@ packages:
'@vueuse/shared@10.11.1': '@vueuse/shared@10.11.1':
resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==} resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==}
'@vueuse/shared@12.5.0':
resolution: {integrity: sha512-vMpcL1lStUU6O+kdj6YdHDixh0odjPAUM15uJ9f7MY781jcYkIwFA4iv2EfoIPO6vBmvutI1HxxAwmf0cx5ISQ==}
abbrev@2.0.0: abbrev@2.0.0:
resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@@ -2152,6 +2215,10 @@ packages:
argparse@2.0.1: argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
aria-hidden@1.2.4:
resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==}
engines: {node: '>=10'}
array-buffer-byte-length@1.0.2: array-buffer-byte-length@1.0.2:
resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -2381,6 +2448,9 @@ packages:
citty@0.1.6: citty@0.1.6:
resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==}
class-variance-authority@0.7.1:
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
clean-regexp@1.0.0: clean-regexp@1.0.0:
resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==}
engines: {node: '>=4'} engines: {node: '>=4'}
@@ -2396,6 +2466,10 @@ packages:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
clsx@2.1.1:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
cluster-key-slot@1.1.2: cluster-key-slot@1.1.2:
resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -3896,6 +3970,11 @@ packages:
lru-cache@5.1.1: lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
lucide-vue-next@0.474.0:
resolution: {integrity: sha512-bQaSBjfJ33xiPQCxCf4JD3rcUgZFgWZzxSY8SScNa4Mcq2vWGlbvQx6icTL1UXRqsxzfoT13RXawePSmgg4iWw==}
peerDependencies:
vue: '>=3.0.1'
lunr@2.3.9: lunr@2.3.9:
resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==}
@@ -4659,6 +4738,11 @@ packages:
queue-tick@1.0.1: queue-tick@1.0.1:
resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==}
radix-vue@1.9.12:
resolution: {integrity: sha512-zkr66Jqxbej4+oR6O/pZRzyM/VZi66ndbyIBZQjJKAXa1lIoYReZJse6W1EEDZKXknD7rXhpS+jM9Sr23lIqfg==}
peerDependencies:
vue: '>= 3.2.0'
radix3@1.1.2: radix3@1.1.2:
resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==}
@@ -4903,6 +4987,9 @@ packages:
setprototypeof@1.2.0: setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
shadcn-nuxt@0.11.3:
resolution: {integrity: sha512-Q0OxqTEbTmtbm/4wV9jd/7DTQYn65AGHkVJZjjHQdHGHTHCwhAbZ2jlCPnMuzJ8kgSsJyXFGNcImAphDCjjV5w==}
shebang-command@2.0.0: shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -5146,6 +5233,14 @@ packages:
peerDependencies: peerDependencies:
tailwindcss: 1 || 2 || 2.0.1-compat || 3 tailwindcss: 1 || 2 || 2.0.1-compat || 3
tailwind-merge@2.6.0:
resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==}
tailwindcss-animate@1.0.7:
resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==}
peerDependencies:
tailwindcss: '>=3.0.0 || insiders'
tailwindcss@3.4.17: tailwindcss@3.4.17:
resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==} resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==}
engines: {node: '>=14.0.0'} engines: {node: '>=14.0.0'}
@@ -5307,6 +5402,11 @@ packages:
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
hasBin: true hasBin: true
typescript@5.7.3:
resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==}
engines: {node: '>=14.17'}
hasBin: true
uc.micro@2.1.0: uc.micro@2.1.0:
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
@@ -6869,6 +6969,26 @@ snapshots:
'@faker-js/faker@8.4.1': {} '@faker-js/faker@8.4.1': {}
'@floating-ui/core@1.6.9':
dependencies:
'@floating-ui/utils': 0.2.9
'@floating-ui/dom@1.6.13':
dependencies:
'@floating-ui/core': 1.6.9
'@floating-ui/utils': 0.2.9
'@floating-ui/utils@0.2.9': {}
'@floating-ui/vue@1.1.6(vue@3.4.8(typescript@5.6.2))':
dependencies:
'@floating-ui/dom': 1.6.13
'@floating-ui/utils': 0.2.9
vue-demi: 0.14.10(vue@3.4.8(typescript@5.6.2))
transitivePeerDependencies:
- '@vue/composition-api'
- vue
'@formatjs/ecma402-abstract@2.3.2': '@formatjs/ecma402-abstract@2.3.2':
dependencies: dependencies:
'@formatjs/fast-memoize': 2.2.6 '@formatjs/fast-memoize': 2.2.6
@@ -6931,6 +7051,14 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@internationalized/date@3.7.0':
dependencies:
'@swc/helpers': 0.5.15
'@internationalized/number@3.6.0':
dependencies:
'@swc/helpers': 0.5.15
'@intlify/bundle-utils@8.0.0(vue-i18n@9.14.2(vue@3.4.8(typescript@5.6.2)))': '@intlify/bundle-utils@8.0.0(vue-i18n@9.14.2(vue@3.4.8(typescript@5.6.2)))':
dependencies: dependencies:
'@intlify/message-compiler': 9.14.2 '@intlify/message-compiler': 9.14.2
@@ -7326,9 +7454,20 @@ snapshots:
- vti - vti
- vue-tsc - vue-tsc
'@nuxtjs/color-mode@3.5.2(magicast@0.3.5)(rollup@4.29.1)':
dependencies:
'@nuxt/kit': 3.15.0(magicast@0.3.5)(rollup@4.29.1)
pathe: 1.1.2
pkg-types: 1.3.0
semver: 7.6.3
transitivePeerDependencies:
- magicast
- rollup
- supports-color
'@nuxtjs/eslint-config-typescript@12.1.0(eslint@8.57.1)(typescript@5.6.2)': '@nuxtjs/eslint-config-typescript@12.1.0(eslint@8.57.1)(typescript@5.6.2)':
dependencies: dependencies:
'@nuxtjs/eslint-config': 12.0.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) '@nuxtjs/eslint-config': 12.0.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1)
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2) '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2)
'@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.6.2) '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.6.2)
eslint: 8.57.1 eslint: 8.57.1
@@ -7341,10 +7480,10 @@ snapshots:
- supports-color - supports-color
- typescript - typescript
'@nuxtjs/eslint-config@12.0.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1)': '@nuxtjs/eslint-config@12.0.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1)':
dependencies: dependencies:
eslint: 8.57.1 eslint: 8.57.1
eslint-config-standard: 17.1.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1))(eslint-plugin-n@15.7.0(eslint@8.57.1))(eslint-plugin-promise@6.6.0(eslint@8.57.1))(eslint@8.57.1) eslint-config-standard: 17.1.0(eslint-plugin-import@2.31.0)(eslint-plugin-n@15.7.0(eslint@8.57.1))(eslint-plugin-promise@6.6.0(eslint@8.57.1))(eslint@8.57.1)
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1)
eslint-plugin-n: 15.7.0(eslint@8.57.1) eslint-plugin-n: 15.7.0(eslint@8.57.1)
eslint-plugin-node: 11.1.0(eslint@8.57.1) eslint-plugin-node: 11.1.0(eslint@8.57.1)
@@ -7379,6 +7518,8 @@ snapshots:
- supports-color - supports-color
- ts-node - ts-node
'@oxc-parser/wasm@0.29.0': {}
'@parcel/watcher-android-arm64@2.5.0': '@parcel/watcher-android-arm64@2.5.0':
optional: true optional: true
@@ -7681,6 +7822,10 @@ snapshots:
magic-string: 0.25.9 magic-string: 0.25.9
string.prototype.matchall: 4.0.12 string.prototype.matchall: 4.0.12
'@swc/helpers@0.5.15':
dependencies:
tslib: 2.8.1
'@tailwindcss/aspect-ratio@0.4.2(tailwindcss@3.4.17)': '@tailwindcss/aspect-ratio@0.4.2(tailwindcss@3.4.17)':
dependencies: dependencies:
tailwindcss: 3.4.17 tailwindcss: 3.4.17
@@ -8168,8 +8313,19 @@ snapshots:
- '@vue/composition-api' - '@vue/composition-api'
- vue - vue
'@vueuse/core@12.5.0(typescript@5.6.2)':
dependencies:
'@types/web-bluetooth': 0.0.20
'@vueuse/metadata': 12.5.0
'@vueuse/shared': 12.5.0(typescript@5.6.2)
vue: 3.5.13(typescript@5.6.2)
transitivePeerDependencies:
- typescript
'@vueuse/metadata@10.11.1': {} '@vueuse/metadata@10.11.1': {}
'@vueuse/metadata@12.5.0': {}
'@vueuse/nuxt@10.11.1(magicast@0.3.5)(nuxt@3.12.4(@parcel/watcher@2.5.0)(@types/node@22.10.5)(db0@0.2.1)(eslint@8.57.1)(ioredis@5.4.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.29.1)(terser@5.37.0)(typescript@5.6.2)(vite@5.4.11(@types/node@22.10.5)(terser@5.37.0))(vue-tsc@2.1.6(typescript@5.6.2)))(rollup@4.29.1)(vue@3.4.8(typescript@5.6.2))': '@vueuse/nuxt@10.11.1(magicast@0.3.5)(nuxt@3.12.4(@parcel/watcher@2.5.0)(@types/node@22.10.5)(db0@0.2.1)(eslint@8.57.1)(ioredis@5.4.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.29.1)(terser@5.37.0)(typescript@5.6.2)(vite@5.4.11(@types/node@22.10.5)(terser@5.37.0))(vue-tsc@2.1.6(typescript@5.6.2)))(rollup@4.29.1)(vue@3.4.8(typescript@5.6.2))':
dependencies: dependencies:
'@nuxt/kit': 3.15.0(magicast@0.3.5)(rollup@4.29.1) '@nuxt/kit': 3.15.0(magicast@0.3.5)(rollup@4.29.1)
@@ -8201,6 +8357,12 @@ snapshots:
- '@vue/composition-api' - '@vue/composition-api'
- vue - vue
'@vueuse/shared@12.5.0(typescript@5.6.2)':
dependencies:
vue: 3.5.13(typescript@5.6.2)
transitivePeerDependencies:
- typescript
abbrev@2.0.0: {} abbrev@2.0.0: {}
abort-controller@3.0.0: abort-controller@3.0.0:
@@ -8293,6 +8455,10 @@ snapshots:
argparse@2.0.1: {} argparse@2.0.1: {}
aria-hidden@1.2.4:
dependencies:
tslib: 2.8.1
array-buffer-byte-length@1.0.2: array-buffer-byte-length@1.0.2:
dependencies: dependencies:
call-bound: 1.0.3 call-bound: 1.0.3
@@ -8580,6 +8746,10 @@ snapshots:
dependencies: dependencies:
consola: 3.3.3 consola: 3.3.3
class-variance-authority@0.7.1:
dependencies:
clsx: 2.1.1
clean-regexp@1.0.0: clean-regexp@1.0.0:
dependencies: dependencies:
escape-string-regexp: 1.0.5 escape-string-regexp: 1.0.5
@@ -8598,6 +8768,8 @@ snapshots:
strip-ansi: 6.0.1 strip-ansi: 6.0.1
wrap-ansi: 7.0.0 wrap-ansi: 7.0.0
clsx@2.1.1: {}
cluster-key-slot@1.1.2: {} cluster-key-slot@1.1.2: {}
co@4.6.0: {} co@4.6.0: {}
@@ -9160,7 +9332,7 @@ snapshots:
dependencies: dependencies:
eslint: 8.57.1 eslint: 8.57.1
eslint-config-standard@17.1.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1))(eslint-plugin-n@15.7.0(eslint@8.57.1))(eslint-plugin-promise@6.6.0(eslint@8.57.1))(eslint@8.57.1): eslint-config-standard@17.1.0(eslint-plugin-import@2.31.0)(eslint-plugin-n@15.7.0(eslint@8.57.1))(eslint-plugin-promise@6.6.0(eslint@8.57.1))(eslint@8.57.1):
dependencies: dependencies:
eslint: 8.57.1 eslint: 8.57.1
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1)
@@ -9191,7 +9363,7 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1): eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1):
dependencies: dependencies:
debug: 3.2.7 debug: 3.2.7
optionalDependencies: optionalDependencies:
@@ -9225,7 +9397,7 @@ snapshots:
doctrine: 2.1.0 doctrine: 2.1.0
eslint: 8.57.1 eslint: 8.57.1
eslint-import-resolver-node: 0.3.9 eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1)
hasown: 2.0.2 hasown: 2.0.2
is-core-module: 2.16.1 is-core-module: 2.16.1
is-glob: 4.0.3 is-glob: 4.0.3
@@ -10259,6 +10431,10 @@ snapshots:
dependencies: dependencies:
yallist: 3.1.1 yallist: 3.1.1
lucide-vue-next@0.474.0(vue@3.4.8(typescript@5.6.2)):
dependencies:
vue: 3.4.8(typescript@5.6.2)
lunr@2.3.9: {} lunr@2.3.9: {}
magic-string-ast@0.6.3: magic-string-ast@0.6.3:
@@ -10596,7 +10772,7 @@ snapshots:
unenv: 1.10.0 unenv: 1.10.0
unimport: 3.14.5(rollup@4.29.1) unimport: 3.14.5(rollup@4.29.1)
unplugin: 1.16.0 unplugin: 1.16.0
unplugin-vue-router: 0.10.9(rollup@4.29.1)(vue-router@4.5.0(vue@3.5.13(typescript@5.6.2)))(vue@3.5.13(typescript@5.6.2)) unplugin-vue-router: 0.10.9(rollup@4.29.1)(vue-router@4.5.0(vue@3.4.8(typescript@5.6.2)))(vue@3.5.13(typescript@5.6.2))
unstorage: 1.14.4(db0@0.2.1)(ioredis@5.4.2) unstorage: 1.14.4(db0@0.2.1)(ioredis@5.4.2)
untyped: 1.5.2 untyped: 1.5.2
vue: 3.5.13(typescript@5.6.2) vue: 3.5.13(typescript@5.6.2)
@@ -11144,6 +11320,23 @@ snapshots:
queue-tick@1.0.1: {} queue-tick@1.0.1: {}
radix-vue@1.9.12(vue@3.4.8(typescript@5.6.2)):
dependencies:
'@floating-ui/dom': 1.6.13
'@floating-ui/vue': 1.1.6(vue@3.4.8(typescript@5.6.2))
'@internationalized/date': 3.7.0
'@internationalized/number': 3.6.0
'@tanstack/vue-virtual': 3.11.2(vue@3.4.8(typescript@5.6.2))
'@vueuse/core': 10.11.1(vue@3.4.8(typescript@5.6.2))
'@vueuse/shared': 10.11.1(vue@3.4.8(typescript@5.6.2))
aria-hidden: 1.2.4
defu: 6.1.4
fast-deep-equal: 3.1.3
nanoid: 5.0.9
vue: 3.4.8(typescript@5.6.2)
transitivePeerDependencies:
- '@vue/composition-api'
radix3@1.1.2: {} radix3@1.1.2: {}
randombytes@2.1.0: randombytes@2.1.0:
@@ -11442,6 +11635,16 @@ snapshots:
setprototypeof@1.2.0: {} setprototypeof@1.2.0: {}
shadcn-nuxt@0.11.3(magicast@0.3.5)(rollup@4.29.1):
dependencies:
'@nuxt/kit': 3.15.0(magicast@0.3.5)(rollup@4.29.1)
'@oxc-parser/wasm': 0.29.0
typescript: 5.7.3
transitivePeerDependencies:
- magicast
- rollup
- supports-color
shebang-command@2.0.0: shebang-command@2.0.0:
dependencies: dependencies:
shebang-regex: 3.0.0 shebang-regex: 3.0.0
@@ -11715,6 +11918,12 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
tailwind-merge@2.6.0: {}
tailwindcss-animate@1.0.7(tailwindcss@3.4.17):
dependencies:
tailwindcss: 3.4.17
tailwindcss@3.4.17: tailwindcss@3.4.17:
dependencies: dependencies:
'@alloc/quick-lru': 5.2.0 '@alloc/quick-lru': 5.2.0
@@ -11902,6 +12111,8 @@ snapshots:
typescript@5.6.2: {} typescript@5.6.2: {}
typescript@5.7.3: {}
uc.micro@2.1.0: {} uc.micro@2.1.0: {}
ufo@1.5.4: {} ufo@1.5.4: {}
@@ -11993,7 +12204,7 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
unplugin-vue-router@0.10.9(rollup@4.29.1)(vue-router@4.5.0(vue@3.5.13(typescript@5.6.2)))(vue@3.5.13(typescript@5.6.2)): unplugin-vue-router@0.10.9(rollup@4.29.1)(vue-router@4.5.0(vue@3.4.8(typescript@5.6.2)))(vue@3.5.13(typescript@5.6.2)):
dependencies: dependencies:
'@babel/types': 7.26.3 '@babel/types': 7.26.3
'@rollup/pluginutils': 5.1.4(rollup@4.29.1) '@rollup/pluginutils': 5.1.4(rollup@4.29.1)
@@ -12010,7 +12221,7 @@ snapshots:
unplugin: 2.0.0-beta.1 unplugin: 2.0.0-beta.1
yaml: 2.7.0 yaml: 2.7.0
optionalDependencies: optionalDependencies:
vue-router: 4.5.0(vue@3.5.13(typescript@5.6.2)) vue-router: 4.5.0(vue@3.4.8(typescript@5.6.2))
transitivePeerDependencies: transitivePeerDependencies:
- rollup - rollup
- vue - vue

View File

@@ -3,7 +3,10 @@ try {
const theme = JSON.parse( const theme = JSON.parse(
localStorage.getItem('homebox/preferences/location') localStorage.getItem('homebox/preferences/location')
).theme; ).theme;
if (theme) document.documentElement.setAttribute('data-theme', theme); if (theme) {
document.documentElement.setAttribute('data-theme', theme);
document.documentElement.classList.add('theme-' + theme);
}
} catch (e) { } catch (e) {
console.error('Failed to set theme', e); console.error('Failed to set theme', e);
} }

View File

@@ -1,8 +1,128 @@
import { config } from "dotenv";
config();
// check if DISABLE_DAISYUI is set to true in the environment
const isDisabled = process.env.DISABLE_DAISYUI === "true";
if (isDisabled) {
console.log("DAISYUI DISABLED");
}
/** @type {import('tailwindcss').Config} */
module.exports = { module.exports = {
content: ["./app.vue", "./{components,pages,layouts}/**/*.{vue,js,ts,jsx,tsx}"], darkMode: ["class"],
darkMode: "class", // or 'media' or 'class' safelist: [
"dark",
"theme-aqua",
"theme-black",
"theme-bumblebee",
"theme-cmyk",
"theme-corporate",
"theme-cupcake",
"theme-cyberpunk",
"theme-dark",
"theme-dracula",
"theme-emerald",
"theme-fantasy",
"theme-forest",
"theme-garden",
"theme-halloween",
"theme-light",
"theme-lofi",
"theme-luxury",
"theme-pastel",
"theme-retro",
"theme-synthwave",
"theme-valentine",
"theme-wireframe",
"theme-autumn",
"theme-business",
"theme-acid",
"theme-lemonade",
"theme-night",
"theme-coffee",
"theme-winter",
"theme-dim",
"theme-nord",
"theme-sunset",
],
prefix: "",
theme: { theme: {
extend: {}, container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
xl: "calc(var(--radius) + 4px)",
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: 0 },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: 0 },
},
"collapsible-down": {
from: { height: 0 },
to: { height: "var(--radix-collapsible-content-height)" },
},
"collapsible-up": {
from: { height: "var(--radix-collapsible-content-height)" },
to: { height: 0 },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
"collapsible-down": "collapsible-down 0.2s ease-in-out",
"collapsible-up": "collapsible-up 0.2s ease-in-out",
},
},
}, },
daisyui: { daisyui: {
themes: [ themes: [
@@ -50,8 +170,12 @@ module.exports = {
"winter", "winter",
], ],
}, },
variants: { plugins: isDisabled
extend: {}, ? [require("@tailwindcss/aspect-ratio"), require("@tailwindcss/typography"), require("tailwindcss-animate")]
}, : [
plugins: [require("@tailwindcss/aspect-ratio"), require("@tailwindcss/typography"), require("daisyui")], require("@tailwindcss/aspect-ratio"),
require("@tailwindcss/typography"),
require("daisyui"),
require("tailwindcss-animate"),
],
}; };