Migrate to Gitea, switch JS tooling to oxlint/oxfmt, lift test coverage to 95%
- 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:
@@ -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 />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user