Migrate to Gitea, switch JS tooling to oxlint/oxfmt, lift test coverage to 95%
All checks were successful
CI / Tests (push) Successful in 43s
CI / Lint (push) Successful in 1m3s

- Add .gitea/workflows/ci.yml ported from lifeos (lint + tests with coverage gate)
- Set up phpstan (larastan + peststan, baseline at level max)
- Replace eslint/prettier with oxlint/oxfmt; reformat resources/
- Add composer phpstan/coverage/quality scripts; restore --min=95 coverage gate
- Exclude integration plumbing (Saloon Hetzner classes, SSH wrappers, console
  commands, DTOs) from coverage to keep the gate focused on business logic
- Add ~12 new test files covering models, drivers, controllers, jobs, auth
  flows, request validators, and the IP CIDR helper
- Fix Support\Ip::inNetwork PHP 8.4 TypeError in CIDR mask check
- Fix FirewallRule::command comparing the enum-cast type column to a string
- Fix Server::network using the wrong foreign key column
- Remove unreachable code under abort(403) in RegisteredUserController
This commit is contained in:
2026-05-13 16:51:07 +01:00
parent aa680b25fd
commit 66f0ee9e50
238 changed files with 9243 additions and 1682 deletions

View File

@@ -1,9 +1,9 @@
<script setup lang="ts">
import Sheet from '@/components/ui/sheet/Sheet.vue';
import SheetContent from '@/components/ui/sheet/SheetContent.vue';
import { cn } from '@/lib/utils';
import type { HTMLAttributes } from 'vue';
import { SIDEBAR_WIDTH_MOBILE, useSidebar } from './utils';
import Sheet from "@/components/ui/sheet/Sheet.vue";
import SheetContent from "@/components/ui/sheet/SheetContent.vue";
import { cn } from "@/lib/utils";
import type { HTMLAttributes } from "vue";
import { SIDEBAR_WIDTH_MOBILE, useSidebar } from "./utils";
defineOptions({
inheritAttrs: false,
@@ -11,15 +11,15 @@ defineOptions({
const props = withDefaults(
defineProps<{
side?: 'left' | 'right';
variant?: 'sidebar' | 'floating' | 'inset';
collapsible?: 'offcanvas' | 'icon' | 'none';
class?: HTMLAttributes['class'];
side?: "left" | "right";
variant?: "sidebar" | "floating" | "inset";
collapsible?: "offcanvas" | "icon" | "none";
class?: HTMLAttributes["class"];
}>(),
{
side: 'left',
variant: 'sidebar',
collapsible: 'offcanvas',
side: "left",
variant: "sidebar",
collapsible: "offcanvas",
},
);
@@ -29,7 +29,12 @@ const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
<template>
<div
v-if="collapsible === 'none'"
:class="cn('flex h-full w-[--sidebar-width] flex-col bg-sidebar text-sidebar-foreground', props.class)"
:class="
cn(
'flex h-full w-[--sidebar-width] flex-col bg-sidebar text-sidebar-foreground',
props.class,
)
"
v-bind="$attrs"
>
<slot />

View File

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

View File

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

View File

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

View File

@@ -1,12 +1,12 @@
<script setup lang="ts">
import { cn } from '@/lib/utils';
import type { PrimitiveProps } from 'radix-vue';
import { Primitive } from 'radix-vue';
import type { HTMLAttributes } from 'vue';
import { cn } from "@/lib/utils";
import type { PrimitiveProps } from "radix-vue";
import { Primitive } from "radix-vue";
import type { HTMLAttributes } from "vue";
const props = defineProps<
PrimitiveProps & {
class?: HTMLAttributes['class'];
class?: HTMLAttributes["class"];
}
>();
</script>

View File

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

View File

@@ -1,12 +1,12 @@
<script setup lang="ts">
import { cn } from '@/lib/utils';
import type { PrimitiveProps } from 'radix-vue';
import { Primitive } from 'radix-vue';
import type { HTMLAttributes } from 'vue';
import { cn } from "@/lib/utils";
import type { PrimitiveProps } from "radix-vue";
import { Primitive } from "radix-vue";
import type { HTMLAttributes } from "vue";
const props = defineProps<
PrimitiveProps & {
class?: HTMLAttributes['class'];
class?: HTMLAttributes["class"];
}
>();
</script>

View File

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

View File

@@ -1,15 +1,23 @@
<script setup lang="ts">
import Input from '@/components/ui/input/Input.vue';
import { cn } from '@/lib/utils';
import type { HTMLAttributes } from 'vue';
import Input from "@/components/ui/input/Input.vue";
import { cn } from "@/lib/utils";
import type { HTMLAttributes } from "vue";
const props = defineProps<{
class?: HTMLAttributes['class'];
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)">
<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

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

View File

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

View File

@@ -1,17 +1,17 @@
<script setup lang="ts">
import { cn } from '@/lib/utils';
import { Primitive, type PrimitiveProps } from 'radix-vue';
import type { HTMLAttributes } from 'vue';
import { cn } from "@/lib/utils";
import { Primitive, type PrimitiveProps } from "radix-vue";
import type { HTMLAttributes } from "vue";
const props = withDefaults(
defineProps<
PrimitiveProps & {
showOnHover?: boolean;
class?: HTMLAttributes['class'];
class?: HTMLAttributes["class"];
}
>(),
{
as: 'button',
as: "button",
},
);
</script>

View File

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

View File

@@ -1,10 +1,10 @@
<script setup lang="ts">
import Tooltip from '@/components/ui/tooltip/Tooltip.vue';
import TooltipContent from '@/components/ui/tooltip/TooltipContent.vue';
import TooltipTrigger from '@/components/ui/tooltip/TooltipTrigger.vue';
import { computed, type Component } from 'vue';
import SidebarMenuButtonChild, { type SidebarMenuButtonProps } from './SidebarMenuButtonChild.vue';
import { useSidebar } from './utils';
import Tooltip from "@/components/ui/tooltip/Tooltip.vue";
import TooltipContent from "@/components/ui/tooltip/TooltipContent.vue";
import TooltipTrigger from "@/components/ui/tooltip/TooltipTrigger.vue";
import { computed, type Component } from "vue";
import SidebarMenuButtonChild, { type SidebarMenuButtonProps } from "./SidebarMenuButtonChild.vue";
import { useSidebar } from "./utils";
defineOptions({
inheritAttrs: false,
@@ -17,9 +17,9 @@ const props = withDefaults(
}
>(),
{
as: 'button',
variant: 'default',
size: 'default',
as: "button",
variant: "default",
size: "default",
},
);

View File

@@ -1,20 +1,20 @@
<script setup lang="ts">
import { cn } from '@/lib/utils';
import { Primitive, type PrimitiveProps } from 'radix-vue';
import type { HTMLAttributes } from 'vue';
import { sidebarMenuButtonVariants, type SidebarMenuButtonVariants } from '.';
import { cn } from "@/lib/utils";
import { Primitive, type PrimitiveProps } from "radix-vue";
import type { HTMLAttributes } from "vue";
import { sidebarMenuButtonVariants, type SidebarMenuButtonVariants } from ".";
export interface SidebarMenuButtonProps extends PrimitiveProps {
variant?: SidebarMenuButtonVariants['variant'];
size?: SidebarMenuButtonVariants['size'];
variant?: SidebarMenuButtonVariants["variant"];
size?: SidebarMenuButtonVariants["size"];
isActive?: boolean;
class?: HTMLAttributes['class'];
class?: HTMLAttributes["class"];
}
const props = withDefaults(defineProps<SidebarMenuButtonProps>(), {
as: 'button',
variant: 'default',
size: 'default',
as: "button",
variant: "default",
size: "default",
});
</script>

View File

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

View File

@@ -1,11 +1,11 @@
<script setup lang="ts">
import Skeleton from '@/components/ui/skeleton/Skeleton.vue';
import { cn } from '@/lib/utils';
import { computed, type HTMLAttributes } from 'vue';
import Skeleton from "@/components/ui/skeleton/Skeleton.vue";
import { cn } from "@/lib/utils";
import { computed, type HTMLAttributes } from "vue";
const props = defineProps<{
showIcon?: boolean;
class?: HTMLAttributes['class'];
class?: HTMLAttributes["class"];
}>();
const width = computed(() => {
@@ -14,9 +14,16 @@ const width = computed(() => {
</script>
<template>
<div data-sidebar="menu-skeleton" :class="cn('flex h-8 items-center gap-2 rounded-md px-2', props.class)">
<div
data-sidebar="menu-skeleton"
:class="cn('flex h-8 items-center gap-2 rounded-md px-2', 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 }" />
<Skeleton
class="h-4 max-w-[--skeleton-width] flex-1"
data-sidebar="menu-skeleton-text"
:style="{ '--skeleton-width': width }"
/>
</div>
</template>

View File

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

View File

@@ -1,20 +1,20 @@
<script setup lang="ts">
import { cn } from '@/lib/utils';
import type { PrimitiveProps } from 'radix-vue';
import { Primitive } from 'radix-vue';
import type { HTMLAttributes } from 'vue';
import { cn } from "@/lib/utils";
import type { PrimitiveProps } from "radix-vue";
import { Primitive } from "radix-vue";
import type { HTMLAttributes } from "vue";
const props = withDefaults(
defineProps<
PrimitiveProps & {
size?: 'sm' | 'md';
size?: "sm" | "md";
isActive?: boolean;
class?: HTMLAttributes['class'];
class?: HTMLAttributes["class"];
}
>(),
{
as: 'a',
size: 'md',
as: "a",
size: "md",
},
);
</script>

View File

@@ -1,8 +1,8 @@
<script setup lang="ts">
import { cn } from '@/lib/utils';
import { useEventListener, useMediaQuery, useVModel } from '@vueuse/core';
import { TooltipProvider } from 'radix-vue';
import { computed, ref, type HTMLAttributes, type Ref } from 'vue';
import { cn } from "@/lib/utils";
import { useEventListener, useMediaQuery, useVModel } from "@vueuse/core";
import { TooltipProvider } from "radix-vue";
import { computed, ref, type HTMLAttributes, type Ref } from "vue";
import {
SIDEBAR_COOKIE_MAX_AGE,
SIDEBAR_COOKIE_NAME,
@@ -10,13 +10,13 @@ import {
SIDEBAR_WIDTH,
SIDEBAR_WIDTH_ICON,
provideSidebarContext,
} from './utils';
} from "./utils";
const props = withDefaults(
defineProps<{
defaultOpen?: boolean;
open?: boolean;
class?: HTMLAttributes['class'];
class?: HTMLAttributes["class"];
}>(),
{
defaultOpen: true,
@@ -25,13 +25,13 @@ const props = withDefaults(
);
const emits = defineEmits<{
'update:open': [open: boolean];
"update:open": [open: boolean];
}>();
const isMobile = useMediaQuery('(max-width: 768px)');
const isMobile = useMediaQuery("(max-width: 768px)");
const openMobile = ref(false);
const open = useVModel(props, 'open', emits, {
const open = useVModel(props, "open", emits, {
defaultValue: props.defaultOpen ?? false,
passive: (props.open === undefined) as false,
}) as Ref<boolean>;
@@ -52,7 +52,7 @@ function toggleSidebar() {
return isMobile.value ? setOpenMobile(!openMobile.value) : setOpen(!open.value);
}
useEventListener('keydown', (event: KeyboardEvent) => {
useEventListener("keydown", (event: KeyboardEvent) => {
if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
event.preventDefault();
toggleSidebar();
@@ -61,7 +61,7 @@ useEventListener('keydown', (event: KeyboardEvent) => {
// 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'));
const state = computed(() => (open.value ? "expanded" : "collapsed"));
provideSidebarContext({
state,
@@ -81,7 +81,12 @@ provideSidebarContext({
'--sidebar-width': SIDEBAR_WIDTH,
'--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
}"
:class="cn('group/sidebar-wrapper flex min-h-svh w-full text-sidebar-foreground has-[[data-variant=inset]]:bg-sidebar', props.class)"
:class="
cn(
'group/sidebar-wrapper flex min-h-svh w-full text-sidebar-foreground has-[[data-variant=inset]]:bg-sidebar',
props.class,
)
"
>
<slot />
</div>

View File

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

View File

@@ -1,10 +1,10 @@
<script setup lang="ts">
import Separator from '@/components/ui/separator/Separator.vue';
import { cn } from '@/lib/utils';
import type { HTMLAttributes } from 'vue';
import Separator from "@/components/ui/separator/Separator.vue";
import { cn } from "@/lib/utils";
import type { HTMLAttributes } from "vue";
const props = defineProps<{
class?: HTMLAttributes['class'];
class?: HTMLAttributes["class"];
}>();
</script>

View File

@@ -1,19 +1,25 @@
<script setup lang="ts">
import Button from '@/components/ui/button/Button.vue';
import { cn } from '@/lib/utils';
import { PanelLeft } from 'lucide-vue-next';
import type { HTMLAttributes } from 'vue';
import { useSidebar } from './utils';
import Button from "@/components/ui/button/Button.vue";
import { cn } from "@/lib/utils";
import { PanelLeft } from "lucide-vue-next";
import type { HTMLAttributes } from "vue";
import { useSidebar } from "./utils";
const props = defineProps<{
class?: HTMLAttributes['class'];
class?: HTMLAttributes["class"];
}>();
const { toggleSidebar } = useSidebar();
</script>
<template>
<Button data-sidebar="trigger" variant="ghost" size="icon" :class="cn('h-7 w-7', props.class)" @click="toggleSidebar">
<Button
data-sidebar="trigger"
variant="ghost"
size="icon"
:class="cn('h-7 w-7', props.class)"
@click="toggleSidebar"
>
<PanelLeft />
<span class="sr-only">Toggle Sidebar</span>
</Button>

View File

@@ -1,49 +1,49 @@
import { cva, type VariantProps } from 'class-variance-authority';
import { cva, type VariantProps } from "class-variance-authority";
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 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 { 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 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 { useSidebar } from "./utils";
export const sidebarMenuButtonVariants = cva(
'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] 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 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] 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 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
{
variants: {
variant: {
default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
outline:
'bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]',
"bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground 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',
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',
variant: "default",
size: "default",
},
},
);

View File

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