Implement Fluid Emerald app redesign
This commit is contained in:
@@ -1,14 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { SidebarInset } from '@/components/ui/sidebar';
|
||||
import type { AppVariant } from '@/types';
|
||||
import type { AppContentProps } from '@/types';
|
||||
|
||||
type Props = {
|
||||
variant?: AppVariant;
|
||||
class?: string;
|
||||
};
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
const props = withDefaults(defineProps<AppContentProps>(), {
|
||||
variant: 'sidebar',
|
||||
});
|
||||
const className = computed(() => props.class);
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
LayoutGrid,
|
||||
Layers3,
|
||||
Menu,
|
||||
Search,
|
||||
Shield,
|
||||
Users,
|
||||
} from 'lucide-vue-next';
|
||||
@@ -33,16 +32,11 @@ import { index as adminUsers } from '@/routes/admin/users';
|
||||
import { index as projectTemplatesIndex } from '@/routes/project-templates';
|
||||
import { index as projectsIndex } from '@/routes/projects';
|
||||
import { index as teamsIndex } from '@/routes/teams';
|
||||
import type { BreadcrumbItem, NavItem } from '@/types';
|
||||
import type { AppHeaderProps, NavItem } from '@/types';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
breadcrumbs?: BreadcrumbItem[];
|
||||
}>(),
|
||||
{
|
||||
breadcrumbs: () => [],
|
||||
},
|
||||
);
|
||||
const props = withDefaults(defineProps<AppHeaderProps>(), {
|
||||
breadcrumbs: () => [],
|
||||
});
|
||||
|
||||
const page = usePage();
|
||||
const auth = computed(() => page.props.auth);
|
||||
@@ -131,19 +125,6 @@ const navItemClasses = (item: NavItem): string =>
|
||||
</SheetHeader>
|
||||
|
||||
<div class="flex flex-col gap-5 p-4">
|
||||
<div class="relative">
|
||||
<Search
|
||||
class="text-on-surface-variant pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2"
|
||||
/>
|
||||
<input
|
||||
disabled
|
||||
aria-label="Search"
|
||||
placeholder="Coming soon"
|
||||
class="border-outline-variant bg-surface-container-low text-on-surface placeholder:text-on-surface-variant h-10 w-full rounded-lg border py-2 pl-9 pr-3 text-sm disabled:cursor-not-allowed disabled:opacity-100"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<nav class="flex flex-col gap-1" aria-label="Main">
|
||||
<Link
|
||||
v-for="item in mainNavItems"
|
||||
@@ -191,19 +172,6 @@ const navItemClasses = (item: NavItem): string =>
|
||||
</nav>
|
||||
|
||||
<div class="ml-auto flex items-center gap-3">
|
||||
<div class="relative hidden sm:block">
|
||||
<Search
|
||||
class="text-on-surface-variant pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2"
|
||||
/>
|
||||
<input
|
||||
disabled
|
||||
aria-label="Search"
|
||||
placeholder="Coming soon"
|
||||
class="border-outline-variant bg-surface-container-low text-on-surface placeholder:text-on-surface-variant h-10 w-48 rounded-lg border py-2 pl-9 pr-3 text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-100 md:w-64"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger :as-child="true">
|
||||
<Button
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { usePage } from '@inertiajs/vue3';
|
||||
import { SidebarProvider } from '@/components/ui/sidebar';
|
||||
import type { AppVariant } from '@/types';
|
||||
import type { AppShellProps } from '@/types';
|
||||
|
||||
type Props = {
|
||||
variant?: AppVariant;
|
||||
};
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
withDefaults(defineProps<AppShellProps>(), {
|
||||
variant: 'sidebar',
|
||||
});
|
||||
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import { Link, usePage } from '@inertiajs/vue3';
|
||||
import {
|
||||
ArrowLeft,
|
||||
BellRing,
|
||||
BookOpen,
|
||||
CirclePlus,
|
||||
Folder,
|
||||
FolderGit2,
|
||||
KanbanSquare,
|
||||
LayoutGrid,
|
||||
Layers3,
|
||||
ListTodo,
|
||||
SlidersHorizontal,
|
||||
Tags,
|
||||
Shield,
|
||||
Users,
|
||||
} from 'lucide-vue-next';
|
||||
@@ -26,12 +33,88 @@ import {
|
||||
import { dashboard } from '@/routes';
|
||||
import { index as adminUsers } from '@/routes/admin/users';
|
||||
import { index as projectTemplatesIndex } from '@/routes/project-templates';
|
||||
import { index as projectsIndex } from '@/routes/projects';
|
||||
import {
|
||||
create as projectsCreate,
|
||||
index as projectsIndex,
|
||||
show as projectShow,
|
||||
} from '@/routes/projects';
|
||||
import { index as projectBacklog } from '@/routes/projects/backlog';
|
||||
import { index as projectBoard } from '@/routes/projects/board';
|
||||
import {
|
||||
board as projectSettingsBoard,
|
||||
general as projectSettingsGeneral,
|
||||
metadata as projectSettingsMetadata,
|
||||
notifications as projectSettingsNotifications,
|
||||
repositories as projectSettingsRepositories,
|
||||
} from '@/routes/projects/settings';
|
||||
import { index as teamsIndex } from '@/routes/teams';
|
||||
import type { NavItem } from '@/types';
|
||||
import type { AppSidebarProps, NavItem } from '@/types';
|
||||
|
||||
const props = defineProps<AppSidebarProps>();
|
||||
|
||||
const page = usePage();
|
||||
|
||||
const isProjectSettings = computed(() => page.url.includes('/settings/'));
|
||||
|
||||
const projectNavItems = computed<NavItem[]>(() => {
|
||||
if (!props.project) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
title: 'Overview',
|
||||
href: projectShow(props.project.id),
|
||||
icon: LayoutGrid,
|
||||
isActive: page.url === projectShow.url(props.project.id),
|
||||
},
|
||||
{
|
||||
title: 'Board',
|
||||
href: projectBoard(props.project.id),
|
||||
icon: KanbanSquare,
|
||||
},
|
||||
{
|
||||
title: 'Backlog',
|
||||
href: projectBacklog(props.project.id),
|
||||
icon: ListTodo,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const projectSettingsNavItems = computed<NavItem[]>(() => {
|
||||
if (!props.project || !isProjectSettings.value) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
title: 'General',
|
||||
href: projectSettingsGeneral(props.project.id),
|
||||
icon: SlidersHorizontal,
|
||||
},
|
||||
{
|
||||
title: 'Board',
|
||||
href: projectSettingsBoard(props.project.id),
|
||||
icon: KanbanSquare,
|
||||
},
|
||||
{
|
||||
title: 'Metadata',
|
||||
href: projectSettingsMetadata(props.project.id),
|
||||
icon: Tags,
|
||||
},
|
||||
{
|
||||
title: 'Repositories',
|
||||
href: projectSettingsRepositories(props.project.id),
|
||||
icon: FolderGit2,
|
||||
},
|
||||
{
|
||||
title: 'Notifications',
|
||||
href: projectSettingsNotifications(props.project.id),
|
||||
icon: BellRing,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const mainNavItems = computed<NavItem[]>(() => {
|
||||
const items: NavItem[] = [
|
||||
{
|
||||
@@ -88,12 +171,12 @@ const footerNavItems: NavItem[] = [
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Sidebar collapsible="icon" variant="inset">
|
||||
<Sidebar collapsible="icon" variant="sidebar" class="border-sidebar-border">
|
||||
<SidebarHeader>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton size="lg" as-child>
|
||||
<Link :href="dashboard()">
|
||||
<Link :href="project ? projectsIndex() : dashboard()">
|
||||
<AppLogo />
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
@@ -102,7 +185,60 @@ const footerNavItems: NavItem[] = [
|
||||
</SidebarHeader>
|
||||
|
||||
<SidebarContent>
|
||||
<NavMain :items="mainNavItems" />
|
||||
<template v-if="project">
|
||||
<div
|
||||
class="border-sidebar-border bg-sidebar-accent/60 mx-3 mb-3 rounded-lg border p-3 group-data-[collapsible=icon]:hidden"
|
||||
>
|
||||
<div
|
||||
class="text-sidebar-foreground/55 text-[0.65rem] font-bold uppercase tracking-[0.12em]"
|
||||
>
|
||||
{{ project.key }}
|
||||
</div>
|
||||
<div class="mt-1 truncate text-sm font-semibold">
|
||||
{{ project.name }}
|
||||
</div>
|
||||
</div>
|
||||
<NavMain :items="projectNavItems" label="Project" />
|
||||
<NavMain
|
||||
v-if="projectSettingsNavItems.length > 0"
|
||||
:items="projectSettingsNavItems"
|
||||
label="Project settings"
|
||||
/>
|
||||
<div class="mt-auto px-3 pb-3">
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton as-child tooltip="All projects">
|
||||
<Link :href="projectsIndex()">
|
||||
<ArrowLeft />
|
||||
<span>All projects</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<NavMain :items="mainNavItems" label="Workspace" />
|
||||
<div
|
||||
v-if="page.props.auth.permissions.projectCreate"
|
||||
class="px-3 pt-3 group-data-[collapsible=icon]:px-2"
|
||||
>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
as-child
|
||||
tooltip="New project"
|
||||
class="bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground"
|
||||
>
|
||||
<Link :href="projectsCreate()">
|
||||
<CirclePlus />
|
||||
<span>New project</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</div>
|
||||
</template>
|
||||
</SidebarContent>
|
||||
|
||||
<SidebarFooter>
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import Breadcrumbs from '@/components/Breadcrumbs.vue';
|
||||
import { SidebarTrigger } from '@/components/ui/sidebar';
|
||||
import type { BreadcrumbItem } from '@/types';
|
||||
import type { AppSidebarHeaderProps } from '@/types';
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
breadcrumbs?: BreadcrumbItem[];
|
||||
}>(),
|
||||
{
|
||||
breadcrumbs: () => [],
|
||||
},
|
||||
);
|
||||
withDefaults(defineProps<AppSidebarHeaderProps>(), {
|
||||
breadcrumbs: () => [],
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header
|
||||
class="border-sidebar-border/70 group-has-data-[collapsible=icon]/sidebar-wrapper:h-12 flex h-16 shrink-0 items-center gap-2 border-b px-6 transition-[width,height] ease-linear md:px-4"
|
||||
class="border-border/70 bg-background/92 sticky top-0 z-20 flex h-14 shrink-0 items-center gap-2 border-b px-4 backdrop-blur-xl transition-[width,height] ease-linear md:px-6"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<SidebarTrigger class="-ml-1" />
|
||||
<div class="flex min-w-0 items-center gap-3">
|
||||
<SidebarTrigger
|
||||
class="text-muted-foreground hover:text-foreground -ml-1"
|
||||
/>
|
||||
<template v-if="breadcrumbs && breadcrumbs.length > 0">
|
||||
<Breadcrumbs :breadcrumbs="breadcrumbs" />
|
||||
</template>
|
||||
|
||||
@@ -13,17 +13,17 @@ const tabs = [
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="inline-flex gap-1 rounded-lg bg-neutral-100 p-1 dark:bg-neutral-800"
|
||||
class="bg-surface-container-low border-outline-variant inline-flex gap-1 rounded-lg border p-1"
|
||||
>
|
||||
<button
|
||||
v-for="{ value, Icon, label } in tabs"
|
||||
:key="value"
|
||||
@click="updateAppearance(value)"
|
||||
:class="[
|
||||
'flex items-center rounded-md px-3.5 py-1.5 transition-colors',
|
||||
'flex items-center rounded-md px-3.5 py-2 text-sm font-medium transition-all',
|
||||
appearance === value
|
||||
? 'shadow-xs bg-white dark:bg-neutral-700 dark:text-neutral-100'
|
||||
: 'text-neutral-500 hover:bg-neutral-200/60 hover:text-black dark:text-neutral-400 dark:hover:bg-neutral-700/60',
|
||||
? 'bg-surface-container-lowest text-foreground shadow-xs ring-outline-variant ring-1'
|
||||
: 'text-muted-foreground hover:bg-surface-container-high hover:text-foreground',
|
||||
]"
|
||||
>
|
||||
<component :is="Icon" class="-ml-1 h-4 w-4" />
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
type Props = {
|
||||
title: string;
|
||||
description?: string;
|
||||
variant?: 'default' | 'small';
|
||||
};
|
||||
import type { HeadingProps } from '@/types';
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
withDefaults(defineProps<HeadingProps>(), {
|
||||
variant: 'default',
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header :class="variant === 'small' ? '' : 'mb-8 space-y-0.5'">
|
||||
<header :class="variant === 'small' ? '' : 'space-y-1.5'">
|
||||
<h2
|
||||
:class="
|
||||
variant === 'small'
|
||||
? 'mb-0.5 text-base font-medium'
|
||||
: 'text-xl font-semibold tracking-tight'
|
||||
? 'mb-0.5 text-base font-semibold tracking-tight'
|
||||
: 'text-2xl font-semibold tracking-[-0.025em] sm:text-[1.75rem]'
|
||||
"
|
||||
>
|
||||
{{ title }}
|
||||
</h2>
|
||||
<p v-if="description" class="text-muted-foreground text-sm">
|
||||
<p
|
||||
v-if="description"
|
||||
class="text-muted-foreground max-w-3xl text-sm leading-6"
|
||||
>
|
||||
{{ description }}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
@@ -25,7 +25,7 @@ defineProps<Props>();
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem v-for="item in items" :key="item.title">
|
||||
<SidebarMenuButton
|
||||
class="text-neutral-600 hover:text-neutral-800 dark:text-neutral-300 dark:hover:text-neutral-100"
|
||||
class="text-sidebar-foreground/60 hover:text-sidebar-foreground"
|
||||
as-child
|
||||
>
|
||||
<a
|
||||
|
||||
@@ -8,23 +8,25 @@ import {
|
||||
SidebarMenuItem,
|
||||
} from '@/components/ui/sidebar';
|
||||
import { useCurrentUrl } from '@/composables/useCurrentUrl';
|
||||
import type { NavItem } from '@/types';
|
||||
import type { NavMainProps } from '@/types';
|
||||
|
||||
defineProps<{
|
||||
items: NavItem[];
|
||||
}>();
|
||||
withDefaults(defineProps<NavMainProps>(), {
|
||||
label: 'Workspace',
|
||||
});
|
||||
|
||||
const { isCurrentUrl } = useCurrentUrl();
|
||||
const { isCurrentOrParentUrl } = useCurrentUrl();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SidebarGroup class="px-2 py-0">
|
||||
<SidebarGroupLabel>Platform</SidebarGroupLabel>
|
||||
<SidebarGroupLabel>{{ label }}</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem v-for="item in items" :key="item.title">
|
||||
<SidebarMenuButton
|
||||
as-child
|
||||
:is-active="isCurrentUrl(item.href)"
|
||||
:is-active="
|
||||
item.isActive ?? isCurrentOrParentUrl(item.href)
|
||||
"
|
||||
:tooltip="item.title"
|
||||
>
|
||||
<Link :href="item.href">
|
||||
|
||||
@@ -1,28 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useInitials } from '@/composables/useInitials';
|
||||
import type { User } from '@/types';
|
||||
import UserAvatar from '@/components/ui/avatar/UserAvatar.vue';
|
||||
import type { UserInfoProps } from '@/types';
|
||||
|
||||
type Props = {
|
||||
user: User;
|
||||
showEmail?: boolean;
|
||||
};
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
withDefaults(defineProps<UserInfoProps>(), {
|
||||
showEmail: false,
|
||||
});
|
||||
|
||||
const { getInitials } = useInitials();
|
||||
|
||||
// Compute whether we should show the avatar image
|
||||
const showAvatar = computed(
|
||||
() => props.user.avatar && props.user.avatar !== '',
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UserAvatar />
|
||||
<UserAvatar :user="user" />
|
||||
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">{{ user.name }}</span>
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
import type { VariantProps } from "class-variance-authority"
|
||||
import { cva } from "class-variance-authority"
|
||||
import type { VariantProps } from "class-variance-authority";
|
||||
import { cva } from "class-variance-authority";
|
||||
|
||||
export { default as Alert } from "./Alert.vue"
|
||||
export { default as AlertDescription } from "./AlertDescription.vue"
|
||||
export { default as AlertTitle } from "./AlertTitle.vue"
|
||||
export { default as Alert } from "./Alert.vue";
|
||||
export { default as AlertDescription } from "./AlertDescription.vue";
|
||||
export { default as AlertTitle } from "./AlertTitle.vue";
|
||||
|
||||
export const alertVariants = cva(
|
||||
"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-card text-card-foreground",
|
||||
destructive:
|
||||
"text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
|
||||
},
|
||||
"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-outline-variant bg-surface-container-lowest text-card-foreground",
|
||||
destructive:
|
||||
"border-destructive/35 text-destructive bg-destructive/8 [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
export type AlertVariants = VariantProps<typeof alertVariants>
|
||||
export type AlertVariants = VariantProps<typeof alertVariants>;
|
||||
|
||||
@@ -6,36 +6,30 @@ import {
|
||||
AvatarImage,
|
||||
} from "@/components/ui/avatar/index";
|
||||
import { usePage } from "@inertiajs/vue3";
|
||||
import { computed, PropType } from "vue";
|
||||
import { UserModel } from "@/types/models/user-model";
|
||||
import { computed } from "vue";
|
||||
import type { UserAvatarProps } from "@/types";
|
||||
|
||||
const props = defineProps({
|
||||
user: {
|
||||
type: Object as PropType<UserModel>,
|
||||
},
|
||||
rounded: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
const props = withDefaults(defineProps<UserAvatarProps>(), {
|
||||
rounded: true,
|
||||
});
|
||||
|
||||
const page = usePage();
|
||||
const auth = computed(() => page.props.auth);
|
||||
|
||||
const user = computed(() => props.user ?? auth.value.user)
|
||||
const user = computed(() => props.user ?? auth.value.user);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Avatar class="size-8 overflow-hidden" :class="rounded ? 'rounded-full' : 'rounded-lg'">
|
||||
<AvatarImage
|
||||
v-if="user.avatar"
|
||||
:src="user.avatar"
|
||||
:alt="user.name"
|
||||
/>
|
||||
<Avatar
|
||||
class="border-outline-variant bg-surface-container-high size-8 overflow-hidden border"
|
||||
:class="rounded ? 'rounded-full' : 'rounded-lg'"
|
||||
>
|
||||
<AvatarImage v-if="user.avatar" :src="user.avatar" :alt="user.name" />
|
||||
<AvatarFallback
|
||||
class="rounded-lg bg-neutral-200 font-semibold text-black dark:bg-neutral-700 dark:text-white"
|
||||
class="bg-primary/12 text-on-primary-container text-xs font-bold dark:text-primary-fixed"
|
||||
:class="rounded ? 'rounded-full' : 'rounded-lg'"
|
||||
>
|
||||
{{ getInitials(auth.user?.name) }}
|
||||
{{ getInitials(user.name) }}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
</template>
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import type { VariantProps } from "class-variance-authority"
|
||||
import { cva } from "class-variance-authority"
|
||||
import type { VariantProps } from "class-variance-authority";
|
||||
import { cva } from "class-variance-authority";
|
||||
|
||||
export { default as Badge } from "./Badge.vue"
|
||||
export { default as Badge } from "./Badge.vue";
|
||||
|
||||
export const badgeVariants = cva(
|
||||
"inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
||||
secondary:
|
||||
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
||||
destructive:
|
||||
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||
outline:
|
||||
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
||||
},
|
||||
"inline-flex items-center justify-center rounded-full border px-2.5 py-1 text-[0.7rem] font-semibold leading-none w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/25 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-primary/20 bg-primary/12 text-on-primary-container dark:text-primary-fixed [a&]:hover:bg-primary/18",
|
||||
secondary:
|
||||
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
||||
destructive:
|
||||
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||
outline:
|
||||
"border-outline-variant bg-surface-container-lowest text-on-surface-variant [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
},
|
||||
)
|
||||
export type BadgeVariants = VariantProps<typeof badgeVariants>
|
||||
);
|
||||
export type BadgeVariants = VariantProps<typeof badgeVariants>;
|
||||
|
||||
@@ -1,38 +1,37 @@
|
||||
import type { VariantProps } from "class-variance-authority"
|
||||
import { cva } from "class-variance-authority"
|
||||
import type { VariantProps } from "class-variance-authority";
|
||||
import { cva } from "class-variance-authority";
|
||||
|
||||
export { default as Button } from "./Button.vue"
|
||||
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 transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||
outline:
|
||||
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost:
|
||||
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
"default": "h-9 px-4 py-2 has-[>svg]:px-3",
|
||||
"sm": "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
||||
"lg": "h-10 rounded-md px-6 has-[>svg]:px-4",
|
||||
"icon": "size-9",
|
||||
"icon-sm": "size-8",
|
||||
"icon-lg": "size-10",
|
||||
},
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-semibold transition-all duration-150 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/25 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"bg-primary text-primary-foreground shadow-[0_6px_18px_rgba(0,110,47,0.14)] hover:-translate-y-px hover:bg-primary/90 hover:shadow-[0_8px_22px_rgba(0,110,47,0.2)]",
|
||||
destructive:
|
||||
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||
outline:
|
||||
"border border-outline-variant bg-surface-container-lowest shadow-xs hover:border-outline hover:bg-surface-container-low hover:text-accent-foreground",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground 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 has-[>svg]:px-3",
|
||||
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
||||
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
||||
icon: "size-9",
|
||||
"icon-sm": "size-8",
|
||||
"icon-lg": "size-10",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
},
|
||||
)
|
||||
export type ButtonVariants = VariantProps<typeof buttonVariants>
|
||||
);
|
||||
export type ButtonVariants = VariantProps<typeof buttonVariants>;
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { cn } from "@/lib/utils"
|
||||
import type { HTMLAttributes } from "vue";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes["class"]
|
||||
}>()
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
data-slot="card"
|
||||
:class="
|
||||
cn(
|
||||
'bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
<div
|
||||
data-slot="card"
|
||||
:class="
|
||||
cn(
|
||||
'bg-surface-container-lowest text-card-foreground border-outline-variant flex flex-col gap-6 rounded-xl border py-6 shadow-[0_1px_2px_rgba(16,24,40,0.03)] transition-[border-color,box-shadow] hover:border-outline/65 hover:shadow-kanban-card',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,53 +1,62 @@
|
||||
<script setup lang="ts">
|
||||
import type { DialogContentEmits, DialogContentProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { X } from "lucide-vue-next"
|
||||
import type { DialogContentEmits, DialogContentProps } from "reka-ui";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import { X } from "lucide-vue-next";
|
||||
import {
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogPortal,
|
||||
useForwardPropsEmits,
|
||||
} from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
import DialogOverlay from "./DialogOverlay.vue"
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogPortal,
|
||||
useForwardPropsEmits,
|
||||
} from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
import DialogOverlay from "./DialogOverlay.vue";
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<DialogContentProps & { class?: HTMLAttributes["class"], showCloseButton?: boolean }>(), {
|
||||
showCloseButton: true,
|
||||
})
|
||||
const emits = defineEmits<DialogContentEmits>()
|
||||
const props = withDefaults(
|
||||
defineProps<
|
||||
DialogContentProps & {
|
||||
class?: HTMLAttributes["class"];
|
||||
showCloseButton?: boolean;
|
||||
}
|
||||
>(),
|
||||
{
|
||||
showCloseButton: true,
|
||||
},
|
||||
);
|
||||
const emits = defineEmits<DialogContentEmits>();
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class")
|
||||
const delegatedProps = reactiveOmit(props, "class");
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogContent
|
||||
data-slot="dialog-content"
|
||||
v-bind="{ ...$attrs, ...forwarded }"
|
||||
:class="
|
||||
cn(
|
||||
'bg-background 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 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
<slot />
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogContent
|
||||
data-slot="dialog-content"
|
||||
v-bind="{ ...$attrs, ...forwarded }"
|
||||
:class="
|
||||
cn(
|
||||
'bg-surface-container-lowest border-outline-variant 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 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-xl border p-6 shadow-2xl duration-200 sm:max-w-lg',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
|
||||
<DialogClose
|
||||
v-if="showCloseButton"
|
||||
data-slot="dialog-close"
|
||||
class="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
||||
>
|
||||
<X />
|
||||
<span class="sr-only">Close</span>
|
||||
</DialogClose>
|
||||
</DialogContent>
|
||||
</DialogPortal>
|
||||
<DialogClose
|
||||
v-if="showCloseButton"
|
||||
data-slot="dialog-close"
|
||||
class="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
||||
>
|
||||
<X />
|
||||
<span class="sr-only">Close</span>
|
||||
</DialogClose>
|
||||
</DialogContent>
|
||||
</DialogPortal>
|
||||
</template>
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
import type { DialogOverlayProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { DialogOverlay } from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
import type { DialogOverlayProps } from "reka-ui";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import { DialogOverlay } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps<DialogOverlayProps & { class?: HTMLAttributes["class"] }>()
|
||||
const props = defineProps<
|
||||
DialogOverlayProps & { class?: HTMLAttributes["class"] }
|
||||
>();
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class")
|
||||
const delegatedProps = reactiveOmit(props, "class");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogOverlay
|
||||
data-slot="dialog-overlay"
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</DialogOverlay>
|
||||
<DialogOverlay
|
||||
data-slot="dialog-overlay"
|
||||
v-bind="delegatedProps"
|
||||
:class="
|
||||
cn(
|
||||
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/60 backdrop-blur-sm',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</DialogOverlay>
|
||||
</template>
|
||||
|
||||
@@ -1,39 +1,49 @@
|
||||
<script setup lang="ts">
|
||||
import type { DropdownMenuContentEmits, DropdownMenuContentProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import type {
|
||||
DropdownMenuContentEmits,
|
||||
DropdownMenuContentProps,
|
||||
} from "reka-ui";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import {
|
||||
DropdownMenuContent,
|
||||
DropdownMenuPortal,
|
||||
useForwardPropsEmits,
|
||||
} from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
DropdownMenuContent,
|
||||
DropdownMenuPortal,
|
||||
useForwardPropsEmits,
|
||||
} from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<DropdownMenuContentProps & { class?: HTMLAttributes["class"] }>(),
|
||||
{
|
||||
sideOffset: 4,
|
||||
},
|
||||
)
|
||||
const emits = defineEmits<DropdownMenuContentEmits>()
|
||||
defineProps<
|
||||
DropdownMenuContentProps & { class?: HTMLAttributes["class"] }
|
||||
>(),
|
||||
{
|
||||
sideOffset: 4,
|
||||
},
|
||||
);
|
||||
const emits = defineEmits<DropdownMenuContentEmits>();
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class")
|
||||
const delegatedProps = reactiveOmit(props, "class");
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuContent
|
||||
data-slot="dropdown-menu-content"
|
||||
v-bind="{ ...$attrs, ...forwarded }"
|
||||
:class="cn('bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--reka-dropdown-menu-content-available-height) min-w-[8rem] origin-(--reka-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenuPortal>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuContent
|
||||
data-slot="dropdown-menu-content"
|
||||
v-bind="{ ...$attrs, ...forwarded }"
|
||||
:class="
|
||||
cn(
|
||||
'bg-popover text-popover-foreground border-outline-variant data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--reka-dropdown-menu-content-available-height) min-w-[8rem] origin-(--reka-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg border p-1.5 shadow-xl',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenuPortal>
|
||||
</template>
|
||||
|
||||
@@ -1,33 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { cn } from "@/lib/utils"
|
||||
import type { HTMLAttributes } from "vue";
|
||||
import { useVModel } from "@vueuse/core";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps<{
|
||||
defaultValue?: string | number
|
||||
modelValue?: string | number | null
|
||||
class?: HTMLAttributes["class"]
|
||||
}>()
|
||||
defaultValue?: string | number;
|
||||
modelValue?: string | number | null;
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: "update:modelValue", payload: string | number): void
|
||||
}>()
|
||||
(e: "update:modelValue", payload: string | number): void;
|
||||
}>();
|
||||
|
||||
const modelValue = useVModel(props, "modelValue", emits, {
|
||||
passive: true,
|
||||
defaultValue: props.defaultValue,
|
||||
})
|
||||
passive: true,
|
||||
defaultValue: props.defaultValue,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input
|
||||
v-model="modelValue"
|
||||
data-slot="input"
|
||||
:class="cn(
|
||||
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
||||
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
|
||||
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
<input
|
||||
v-model="modelValue"
|
||||
data-slot="input"
|
||||
:class="
|
||||
cn(
|
||||
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground border-input bg-surface-container-lowest h-10 w-full min-w-0 rounded-md border px-3 py-1 text-base shadow-xs transition-[color,box-shadow,background-color] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
||||
'focus-visible:border-ring focus-visible:ring-ring/25 focus-visible:ring-[3px]',
|
||||
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,51 +1,60 @@
|
||||
<script setup lang="ts">
|
||||
import type { SelectContentEmits, SelectContentProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import type { SelectContentEmits, SelectContentProps } from "reka-ui";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import {
|
||||
SelectContent,
|
||||
SelectPortal,
|
||||
SelectViewport,
|
||||
useForwardPropsEmits,
|
||||
} from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { SelectScrollDownButton, SelectScrollUpButton } from "."
|
||||
SelectContent,
|
||||
SelectPortal,
|
||||
SelectViewport,
|
||||
useForwardPropsEmits,
|
||||
} from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { SelectScrollDownButton, SelectScrollUpButton } from ".";
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<SelectContentProps & { class?: HTMLAttributes["class"] }>(),
|
||||
{
|
||||
position: "popper",
|
||||
},
|
||||
)
|
||||
const emits = defineEmits<SelectContentEmits>()
|
||||
defineProps<SelectContentProps & { class?: HTMLAttributes["class"] }>(),
|
||||
{
|
||||
position: "popper",
|
||||
},
|
||||
);
|
||||
const emits = defineEmits<SelectContentEmits>();
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class")
|
||||
const delegatedProps = reactiveOmit(props, "class");
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SelectPortal>
|
||||
<SelectContent
|
||||
data-slot="select-content"
|
||||
v-bind="{ ...$attrs, ...forwarded }"
|
||||
:class="cn(
|
||||
'bg-popover text-popover-foreground 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 relative z-50 max-h-(--reka-select-content-available-height) min-w-[8rem] overflow-x-hidden overflow-y-auto rounded-md border shadow-md',
|
||||
position === 'popper'
|
||||
&& 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectViewport :class="cn('p-1', position === 'popper' && 'h-[var(--reka-select-trigger-height)] w-full min-w-[var(--reka-select-trigger-width)] scroll-my-1')">
|
||||
<slot />
|
||||
</SelectViewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectContent>
|
||||
</SelectPortal>
|
||||
<SelectPortal>
|
||||
<SelectContent
|
||||
data-slot="select-content"
|
||||
v-bind="{ ...$attrs, ...forwarded }"
|
||||
:class="
|
||||
cn(
|
||||
'bg-popover text-popover-foreground border-outline-variant 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 relative z-50 max-h-(--reka-select-content-available-height) min-w-[8rem] overflow-x-hidden overflow-y-auto rounded-lg border shadow-xl',
|
||||
position === 'popper' &&
|
||||
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectViewport
|
||||
:class="
|
||||
cn(
|
||||
'p-1',
|
||||
position === 'popper' &&
|
||||
'h-[var(--reka-select-trigger-height)] w-full min-w-[var(--reka-select-trigger-width)] scroll-my-1',
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</SelectViewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectContent>
|
||||
</SelectPortal>
|
||||
</template>
|
||||
|
||||
@@ -1,33 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
import type { SelectTriggerProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { ChevronDown } from "lucide-vue-next"
|
||||
import { SelectIcon, SelectTrigger, useForwardProps } from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
import type { SelectTriggerProps } from "reka-ui";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import { ChevronDown } from "lucide-vue-next";
|
||||
import { SelectIcon, SelectTrigger, useForwardProps } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<SelectTriggerProps & { class?: HTMLAttributes["class"], size?: "sm" | "default" }>(),
|
||||
{ size: "default" },
|
||||
)
|
||||
defineProps<
|
||||
SelectTriggerProps & {
|
||||
class?: HTMLAttributes["class"];
|
||||
size?: "sm" | "default";
|
||||
}
|
||||
>(),
|
||||
{ size: "default" },
|
||||
);
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class", "size")
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
const delegatedProps = reactiveOmit(props, "class", "size");
|
||||
const forwardedProps = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SelectTrigger
|
||||
data-slot="select-trigger"
|
||||
:data-size="size"
|
||||
v-bind="forwardedProps"
|
||||
:class="cn(
|
||||
'border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*=\'text-\'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
<slot />
|
||||
<SelectIcon as-child>
|
||||
<ChevronDown class="size-4 opacity-50" />
|
||||
</SelectIcon>
|
||||
</SelectTrigger>
|
||||
<SelectTrigger
|
||||
data-slot="select-trigger"
|
||||
:data-size="size"
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn(
|
||||
'border-input bg-surface-container-lowest data-[placeholder]:text-muted-foreground [&_svg:not([class*=\'text-\'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/25 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive flex w-fit items-center justify-between gap-2 rounded-md border px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow,background-color] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-10 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
<SelectIcon as-child>
|
||||
<ChevronDown class="size-4 opacity-50" />
|
||||
</SelectIcon>
|
||||
</SelectTrigger>
|
||||
</template>
|
||||
|
||||
@@ -1,62 +1,65 @@
|
||||
<script setup lang="ts">
|
||||
import type { DialogContentEmits, DialogContentProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { X } from "lucide-vue-next"
|
||||
import type { DialogContentEmits, DialogContentProps } from "reka-ui";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import { X } from "lucide-vue-next";
|
||||
import {
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogPortal,
|
||||
useForwardPropsEmits,
|
||||
} from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
import SheetOverlay from "./SheetOverlay.vue"
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogPortal,
|
||||
useForwardPropsEmits,
|
||||
} from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
import SheetOverlay from "./SheetOverlay.vue";
|
||||
|
||||
interface SheetContentProps extends DialogContentProps {
|
||||
class?: HTMLAttributes["class"]
|
||||
side?: "top" | "right" | "bottom" | "left"
|
||||
class?: HTMLAttributes["class"];
|
||||
side?: "top" | "right" | "bottom" | "left";
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<SheetContentProps>(), {
|
||||
side: "right",
|
||||
})
|
||||
const emits = defineEmits<DialogContentEmits>()
|
||||
side: "right",
|
||||
});
|
||||
const emits = defineEmits<DialogContentEmits>();
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class", "side")
|
||||
const delegatedProps = reactiveOmit(props, "class", "side");
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogPortal>
|
||||
<SheetOverlay />
|
||||
<DialogContent
|
||||
data-slot="sheet-content"
|
||||
:class="cn(
|
||||
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
|
||||
side === 'right'
|
||||
&& 'data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm',
|
||||
side === 'left'
|
||||
&& 'data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm',
|
||||
side === 'top'
|
||||
&& 'data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b',
|
||||
side === 'bottom'
|
||||
&& 'data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t',
|
||||
props.class)"
|
||||
v-bind="{ ...$attrs, ...forwarded }"
|
||||
>
|
||||
<slot />
|
||||
<DialogPortal>
|
||||
<SheetOverlay />
|
||||
<DialogContent
|
||||
data-slot="sheet-content"
|
||||
:class="
|
||||
cn(
|
||||
'bg-surface-container-lowest data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-2xl transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
|
||||
side === 'right' &&
|
||||
'data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm',
|
||||
side === 'left' &&
|
||||
'data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm',
|
||||
side === 'top' &&
|
||||
'data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b',
|
||||
side === 'bottom' &&
|
||||
'data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
v-bind="{ ...$attrs, ...forwarded }"
|
||||
>
|
||||
<slot />
|
||||
|
||||
<DialogClose
|
||||
class="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none"
|
||||
>
|
||||
<X class="size-4" />
|
||||
<span class="sr-only">Close</span>
|
||||
</DialogClose>
|
||||
</DialogContent>
|
||||
</DialogPortal>
|
||||
<DialogClose
|
||||
class="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none"
|
||||
>
|
||||
<X class="size-4" />
|
||||
<span class="sr-only">Close</span>
|
||||
</DialogClose>
|
||||
</DialogContent>
|
||||
</DialogPortal>
|
||||
</template>
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
import type { DialogOverlayProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { DialogOverlay } from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
import type { DialogOverlayProps } from "reka-ui";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import { DialogOverlay } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps<DialogOverlayProps & { class?: HTMLAttributes["class"] }>()
|
||||
const props = defineProps<
|
||||
DialogOverlayProps & { class?: HTMLAttributes["class"] }
|
||||
>();
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class")
|
||||
const delegatedProps = reactiveOmit(props, "class");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogOverlay
|
||||
data-slot="sheet-overlay"
|
||||
:class="cn('data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80', props.class)"
|
||||
v-bind="delegatedProps"
|
||||
>
|
||||
<slot />
|
||||
</DialogOverlay>
|
||||
<DialogOverlay
|
||||
data-slot="sheet-overlay"
|
||||
:class="
|
||||
cn(
|
||||
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/60 backdrop-blur-sm',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
v-bind="delegatedProps"
|
||||
>
|
||||
<slot />
|
||||
</DialogOverlay>
|
||||
</template>
|
||||
|
||||
@@ -1,60 +1,63 @@
|
||||
import type { VariantProps } from "class-variance-authority"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { cva } from "class-variance-authority"
|
||||
import type { VariantProps } from "class-variance-authority";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
import { cva } from "class-variance-authority";
|
||||
|
||||
export interface SidebarProps {
|
||||
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"];
|
||||
}
|
||||
|
||||
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-hidden 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",
|
||||
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))]",
|
||||
},
|
||||
size: {
|
||||
default: "h-8 text-sm",
|
||||
sm: "h-7 text-xs",
|
||||
lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!",
|
||||
},
|
||||
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding,background-color,color] 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-primary/12 data-[active=true]:font-semibold data-[active=true]:text-primary dark:data-[active=true]:text-primary-fixed 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",
|
||||
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))]",
|
||||
},
|
||||
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",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
export type SidebarMenuButtonVariants = VariantProps<typeof sidebarMenuButtonVariants>
|
||||
export type SidebarMenuButtonVariants = VariantProps<
|
||||
typeof sidebarMenuButtonVariants
|
||||
>;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { createContext } from "reka-ui"
|
||||
import type { ComputedRef, Ref } from 'vue';
|
||||
import { createContext } from "reka-ui";
|
||||
import type { ComputedRef, Ref } from "vue";
|
||||
|
||||
export type SidebarContext = {
|
||||
state: ComputedRef<'expanded' | 'collapsed'>;
|
||||
state: ComputedRef<"expanded" | "collapsed">;
|
||||
open: Ref<boolean>;
|
||||
setOpen: (value: boolean) => void;
|
||||
isMobile: Ref<boolean>;
|
||||
@@ -11,11 +11,12 @@ export type SidebarContext = {
|
||||
toggleSidebar: () => void;
|
||||
};
|
||||
|
||||
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_COOKIE_NAME = "sidebar_state";
|
||||
export const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
|
||||
export const SIDEBAR_WIDTH = "15.5rem";
|
||||
export const SIDEBAR_WIDTH_MOBILE = "18rem";
|
||||
export const SIDEBAR_WIDTH_ICON = "3rem";
|
||||
export const SIDEBAR_KEYBOARD_SHORTCUT = "b";
|
||||
|
||||
export const [useSidebar, provideSidebarContext] = createContext<SidebarContext>("Sidebar")
|
||||
export const [useSidebar, provideSidebarContext] =
|
||||
createContext<SidebarContext>("Sidebar");
|
||||
|
||||
Reference in New Issue
Block a user