@edgedev/create-edge-app 1.0.48 → 1.0.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.nvmrc +1 -0
- package/components/ui/sidebar/Sidebar.vue +85 -0
- package/components/ui/sidebar/SidebarContent.vue +17 -0
- package/components/ui/sidebar/SidebarFooter.vue +17 -0
- package/components/ui/sidebar/SidebarGroup.vue +17 -0
- package/components/ui/sidebar/SidebarGroupAction.vue +27 -0
- package/components/ui/sidebar/SidebarGroupContent.vue +17 -0
- package/components/ui/sidebar/SidebarGroupLabel.vue +24 -0
- package/components/ui/sidebar/SidebarHeader.vue +17 -0
- package/components/ui/sidebar/SidebarInput.vue +21 -0
- package/components/ui/sidebar/SidebarInset.vue +20 -0
- package/components/ui/sidebar/SidebarMenu.vue +17 -0
- package/components/ui/sidebar/SidebarMenuAction.vue +34 -0
- package/components/ui/sidebar/SidebarMenuBadge.vue +25 -0
- package/components/ui/sidebar/SidebarMenuButton.vue +49 -0
- package/components/ui/sidebar/SidebarMenuButtonChild.vue +33 -0
- package/components/ui/sidebar/SidebarMenuItem.vue +17 -0
- package/components/ui/sidebar/SidebarMenuSkeleton.vue +33 -0
- package/components/ui/sidebar/SidebarMenuSub.vue +21 -0
- package/components/ui/sidebar/SidebarMenuSubButton.vue +35 -0
- package/components/ui/sidebar/SidebarMenuSubItem.vue +9 -0
- package/components/ui/sidebar/SidebarProvider.vue +80 -0
- package/components/ui/sidebar/SidebarRail.vue +32 -0
- package/components/ui/sidebar/SidebarSeparator.vue +18 -0
- package/components/ui/sidebar/SidebarTrigger.vue +26 -0
- package/components/ui/sidebar/index.ts +59 -0
- package/components/ui/sidebar/utils.ts +19 -0
- package/firebase.json +1 -0
- package/package.json +1 -1
- package/firestore.rules +0 -342
- package/firestore.rules.backup +0 -323
- package/functions/.env.dev +0 -7
- package/functions/.env.prod +0 -7
- package/functions/index.js +0 -7
- package/functions/package.json +0 -37
- package/functions/stripe.js +0 -103
- package/storage.rules +0 -61
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
22.13.1
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { SidebarProps } from '.'
|
|
3
|
+
import { Sheet, SheetContent } from '@/components/ui/sheet'
|
|
4
|
+
import { cn } from '@/lib/utils'
|
|
5
|
+
import { SIDEBAR_WIDTH_MOBILE, useSidebar } from './utils'
|
|
6
|
+
|
|
7
|
+
defineOptions({
|
|
8
|
+
inheritAttrs: false,
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
const props = withDefaults(defineProps<SidebarProps>(), {
|
|
12
|
+
side: 'left',
|
|
13
|
+
variant: 'sidebar',
|
|
14
|
+
collapsible: 'offcanvas',
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<div
|
|
22
|
+
v-if="collapsible === 'none'"
|
|
23
|
+
:class="cn('flex h-full w-[--sidebar-width] flex-col bg-sidebar text-sidebar-foreground', props.class)"
|
|
24
|
+
v-bind="$attrs"
|
|
25
|
+
>
|
|
26
|
+
<slot />
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<Sheet v-else-if="isMobile" :open="openMobile" v-bind="$attrs" @update:open="setOpenMobile">
|
|
30
|
+
<SheetContent
|
|
31
|
+
data-sidebar="sidebar"
|
|
32
|
+
data-mobile="true"
|
|
33
|
+
:side="side"
|
|
34
|
+
class="w-[--sidebar-width] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
|
|
35
|
+
:style="{
|
|
36
|
+
'--sidebar-width': SIDEBAR_WIDTH_MOBILE,
|
|
37
|
+
}"
|
|
38
|
+
>
|
|
39
|
+
<div class="flex h-full w-full flex-col">
|
|
40
|
+
<slot />
|
|
41
|
+
</div>
|
|
42
|
+
</SheetContent>
|
|
43
|
+
</Sheet>
|
|
44
|
+
|
|
45
|
+
<div
|
|
46
|
+
v-else class="group peer hidden md:block"
|
|
47
|
+
:data-state="state"
|
|
48
|
+
:data-collapsible="state === 'collapsed' ? collapsible : ''"
|
|
49
|
+
:data-variant="variant"
|
|
50
|
+
:data-side="side"
|
|
51
|
+
>
|
|
52
|
+
<!-- This is what handles the sidebar gap on desktop -->
|
|
53
|
+
<div
|
|
54
|
+
:class="cn(
|
|
55
|
+
'duration-200 relative h-svh w-[--sidebar-width] bg-transparent transition-[width] ease-linear',
|
|
56
|
+
'group-data-[collapsible=offcanvas]:w-0',
|
|
57
|
+
'group-data-[side=right]:rotate-180',
|
|
58
|
+
variant === 'floating' || variant === 'inset'
|
|
59
|
+
? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]'
|
|
60
|
+
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon]',
|
|
61
|
+
)"
|
|
62
|
+
/>
|
|
63
|
+
<div
|
|
64
|
+
:class="cn(
|
|
65
|
+
'duration-200 fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width] transition-[left,right,width] ease-linear md:flex',
|
|
66
|
+
side === 'left'
|
|
67
|
+
? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
|
|
68
|
+
: 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
|
|
69
|
+
// Adjust the padding for floating and inset variants.
|
|
70
|
+
variant === 'floating' || variant === 'inset'
|
|
71
|
+
? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]'
|
|
72
|
+
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l',
|
|
73
|
+
props.class,
|
|
74
|
+
)"
|
|
75
|
+
v-bind="$attrs"
|
|
76
|
+
>
|
|
77
|
+
<div
|
|
78
|
+
data-sidebar="sidebar"
|
|
79
|
+
class="flex h-full w-full flex-col text-sidebar-foreground bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow"
|
|
80
|
+
>
|
|
81
|
+
<slot />
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'vue'
|
|
3
|
+
import { cn } from '@/lib/utils'
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
class?: HTMLAttributes['class']
|
|
7
|
+
}>()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<div
|
|
12
|
+
data-sidebar="content"
|
|
13
|
+
:class="cn('flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden', props.class)"
|
|
14
|
+
>
|
|
15
|
+
<slot />
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'vue'
|
|
3
|
+
import { cn } from '@/lib/utils'
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
class?: HTMLAttributes['class']
|
|
7
|
+
}>()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<div
|
|
12
|
+
data-sidebar="footer"
|
|
13
|
+
:class="cn('flex flex-col gap-2 p-2', props.class)"
|
|
14
|
+
>
|
|
15
|
+
<slot />
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'vue'
|
|
3
|
+
import { cn } from '@/lib/utils'
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
class?: HTMLAttributes['class']
|
|
7
|
+
}>()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<div
|
|
12
|
+
data-sidebar="group"
|
|
13
|
+
:class="cn('relative flex w-full min-w-0 flex-col p-2', props.class)"
|
|
14
|
+
>
|
|
15
|
+
<slot />
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { PrimitiveProps } from 'radix-vue'
|
|
3
|
+
import type { HTMLAttributes } from 'vue'
|
|
4
|
+
import { cn } from '@/lib/utils'
|
|
5
|
+
import { Primitive } from 'radix-vue'
|
|
6
|
+
|
|
7
|
+
const props = defineProps<PrimitiveProps & {
|
|
8
|
+
class?: HTMLAttributes['class']
|
|
9
|
+
}>()
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<Primitive
|
|
14
|
+
data-sidebar="group-action"
|
|
15
|
+
:as="as"
|
|
16
|
+
:as-child="asChild"
|
|
17
|
+
:class="cn(
|
|
18
|
+
'absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
|
|
19
|
+
// Increases the hit area of the button on mobile.
|
|
20
|
+
'after:absolute after:-inset-2 after:md:hidden',
|
|
21
|
+
'group-data-[collapsible=icon]:hidden',
|
|
22
|
+
props.class,
|
|
23
|
+
)"
|
|
24
|
+
>
|
|
25
|
+
<slot />
|
|
26
|
+
</Primitive>
|
|
27
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'vue'
|
|
3
|
+
import { cn } from '@/lib/utils'
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
class?: HTMLAttributes['class']
|
|
7
|
+
}>()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<div
|
|
12
|
+
data-sidebar="group-content"
|
|
13
|
+
:class="cn('w-full text-sm', props.class)"
|
|
14
|
+
>
|
|
15
|
+
<slot />
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { PrimitiveProps } from 'radix-vue'
|
|
3
|
+
import type { HTMLAttributes } from 'vue'
|
|
4
|
+
import { cn } from '@/lib/utils'
|
|
5
|
+
import { Primitive } from 'radix-vue'
|
|
6
|
+
|
|
7
|
+
const props = defineProps<PrimitiveProps & {
|
|
8
|
+
class?: HTMLAttributes['class']
|
|
9
|
+
}>()
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<Primitive
|
|
14
|
+
data-sidebar="group-label"
|
|
15
|
+
:as="as"
|
|
16
|
+
:as-child="asChild"
|
|
17
|
+
:class="cn(
|
|
18
|
+
'duration-200 flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opa] ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
|
|
19
|
+
'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
|
|
20
|
+
props.class)"
|
|
21
|
+
>
|
|
22
|
+
<slot />
|
|
23
|
+
</Primitive>
|
|
24
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'vue'
|
|
3
|
+
import { cn } from '@/lib/utils'
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
class?: HTMLAttributes['class']
|
|
7
|
+
}>()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<div
|
|
12
|
+
data-sidebar="header"
|
|
13
|
+
:class="cn('flex flex-col gap-2 p-2', props.class)"
|
|
14
|
+
>
|
|
15
|
+
<slot />
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'vue'
|
|
3
|
+
import { Input } from '@/components/ui/input'
|
|
4
|
+
import { cn } from '@/lib/utils'
|
|
5
|
+
|
|
6
|
+
const props = defineProps<{
|
|
7
|
+
class?: HTMLAttributes['class']
|
|
8
|
+
}>()
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<template>
|
|
12
|
+
<Input
|
|
13
|
+
data-sidebar="input"
|
|
14
|
+
:class="cn(
|
|
15
|
+
'h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring',
|
|
16
|
+
props.class,
|
|
17
|
+
)"
|
|
18
|
+
>
|
|
19
|
+
<slot />
|
|
20
|
+
</Input>
|
|
21
|
+
</template>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'vue'
|
|
3
|
+
import { cn } from '@/lib/utils'
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
class?: HTMLAttributes['class']
|
|
7
|
+
}>()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<main
|
|
12
|
+
:class="cn(
|
|
13
|
+
'relative flex min-h-svh flex-1 flex-col bg-background',
|
|
14
|
+
'peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow',
|
|
15
|
+
props.class,
|
|
16
|
+
)"
|
|
17
|
+
>
|
|
18
|
+
<slot />
|
|
19
|
+
</main>
|
|
20
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'vue'
|
|
3
|
+
import { cn } from '@/lib/utils'
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
class?: HTMLAttributes['class']
|
|
7
|
+
}>()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<ul
|
|
12
|
+
data-sidebar="menu"
|
|
13
|
+
:class="cn('flex w-full min-w-0 flex-col gap-1', props.class)"
|
|
14
|
+
>
|
|
15
|
+
<slot />
|
|
16
|
+
</ul>
|
|
17
|
+
</template>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'vue'
|
|
3
|
+
import { cn } from '@/lib/utils'
|
|
4
|
+
import { Primitive, type PrimitiveProps } from 'radix-vue'
|
|
5
|
+
|
|
6
|
+
const props = withDefaults(defineProps<PrimitiveProps & {
|
|
7
|
+
showOnHover?: boolean
|
|
8
|
+
class?: HTMLAttributes['class']
|
|
9
|
+
}>(), {
|
|
10
|
+
as: 'button',
|
|
11
|
+
})
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<Primitive
|
|
16
|
+
data-sidebar="menu-action"
|
|
17
|
+
:class="cn(
|
|
18
|
+
'absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0',
|
|
19
|
+
// Increases the hit area of the button on mobile.
|
|
20
|
+
'after:absolute after:-inset-2 after:md:hidden',
|
|
21
|
+
'peer-data-[size=sm]/menu-button:top-1',
|
|
22
|
+
'peer-data-[size=default]/menu-button:top-1.5',
|
|
23
|
+
'peer-data-[size=lg]/menu-button:top-2.5',
|
|
24
|
+
'group-data-[collapsible=icon]:hidden',
|
|
25
|
+
showOnHover
|
|
26
|
+
&& 'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0',
|
|
27
|
+
props.class,
|
|
28
|
+
)"
|
|
29
|
+
:as="as"
|
|
30
|
+
:as-child="asChild"
|
|
31
|
+
>
|
|
32
|
+
<slot />
|
|
33
|
+
</Primitive>
|
|
34
|
+
</template>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'vue'
|
|
3
|
+
import { cn } from '@/lib/utils'
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
class?: HTMLAttributes['class']
|
|
7
|
+
}>()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<div
|
|
12
|
+
data-sidebar="menu-badge"
|
|
13
|
+
:class="cn(
|
|
14
|
+
'absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground select-none pointer-events-none',
|
|
15
|
+
'peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground',
|
|
16
|
+
'peer-data-[size=sm]/menu-button:top-1',
|
|
17
|
+
'peer-data-[size=default]/menu-button:top-1.5',
|
|
18
|
+
'peer-data-[size=lg]/menu-button:top-2.5',
|
|
19
|
+
'group-data-[collapsible=icon]:hidden',
|
|
20
|
+
props.class,
|
|
21
|
+
)"
|
|
22
|
+
>
|
|
23
|
+
<slot />
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
|
3
|
+
import { type Component, computed } from 'vue'
|
|
4
|
+
import SidebarMenuButtonChild, { type SidebarMenuButtonProps } from './SidebarMenuButtonChild.vue'
|
|
5
|
+
import { useSidebar } from './utils'
|
|
6
|
+
|
|
7
|
+
defineOptions({
|
|
8
|
+
inheritAttrs: false,
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
const props = withDefaults(defineProps<SidebarMenuButtonProps & {
|
|
12
|
+
tooltip?: string | Component
|
|
13
|
+
}>(), {
|
|
14
|
+
as: 'button',
|
|
15
|
+
variant: 'default',
|
|
16
|
+
size: 'default',
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const { isMobile, state } = useSidebar()
|
|
20
|
+
|
|
21
|
+
const delegatedProps = computed(() => {
|
|
22
|
+
const { tooltip, ...delegated } = props
|
|
23
|
+
return delegated
|
|
24
|
+
})
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<template>
|
|
28
|
+
<SidebarMenuButtonChild v-if="!tooltip" v-bind="{ ...delegatedProps, ...$attrs }">
|
|
29
|
+
<slot />
|
|
30
|
+
</SidebarMenuButtonChild>
|
|
31
|
+
|
|
32
|
+
<Tooltip v-else>
|
|
33
|
+
<TooltipTrigger as-child>
|
|
34
|
+
<SidebarMenuButtonChild v-bind="{ ...delegatedProps, ...$attrs }">
|
|
35
|
+
<slot />
|
|
36
|
+
</SidebarMenuButtonChild>
|
|
37
|
+
</TooltipTrigger>
|
|
38
|
+
<TooltipContent
|
|
39
|
+
side="right"
|
|
40
|
+
align="center"
|
|
41
|
+
:hidden="state !== 'collapsed' || isMobile"
|
|
42
|
+
>
|
|
43
|
+
<template v-if="typeof tooltip === 'string'">
|
|
44
|
+
{{ tooltip }}
|
|
45
|
+
</template>
|
|
46
|
+
<component :is="tooltip" v-else />
|
|
47
|
+
</TooltipContent>
|
|
48
|
+
</Tooltip>
|
|
49
|
+
</template>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'vue'
|
|
3
|
+
import { cn } from '@/lib/utils'
|
|
4
|
+
import { Primitive, type PrimitiveProps } from 'radix-vue'
|
|
5
|
+
import { type SidebarMenuButtonVariants, sidebarMenuButtonVariants } from '.'
|
|
6
|
+
|
|
7
|
+
export interface SidebarMenuButtonProps extends PrimitiveProps {
|
|
8
|
+
variant?: SidebarMenuButtonVariants['variant']
|
|
9
|
+
size?: SidebarMenuButtonVariants['size']
|
|
10
|
+
isActive?: boolean
|
|
11
|
+
class?: HTMLAttributes['class']
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const props = withDefaults(defineProps<SidebarMenuButtonProps>(), {
|
|
15
|
+
as: 'button',
|
|
16
|
+
variant: 'default',
|
|
17
|
+
size: 'default',
|
|
18
|
+
})
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<template>
|
|
22
|
+
<Primitive
|
|
23
|
+
data-sidebar="menu-button"
|
|
24
|
+
:data-size="size"
|
|
25
|
+
:data-active="isActive"
|
|
26
|
+
:class="cn(sidebarMenuButtonVariants({ variant, size }), props.class)"
|
|
27
|
+
:as="as"
|
|
28
|
+
:as-child="asChild"
|
|
29
|
+
v-bind="$attrs"
|
|
30
|
+
>
|
|
31
|
+
<slot />
|
|
32
|
+
</Primitive>
|
|
33
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'vue'
|
|
3
|
+
import { cn } from '@/lib/utils'
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
class?: HTMLAttributes['class']
|
|
7
|
+
}>()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<li
|
|
12
|
+
data-sidebar="menu-item"
|
|
13
|
+
:class="cn('group/menu-item relative', props.class)"
|
|
14
|
+
>
|
|
15
|
+
<slot />
|
|
16
|
+
</li>
|
|
17
|
+
</template>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { Skeleton } from '@/components/ui/skeleton'
|
|
3
|
+
import { cn } from '@/lib/utils'
|
|
4
|
+
import { computed, type HTMLAttributes } from 'vue'
|
|
5
|
+
|
|
6
|
+
const props = defineProps<{
|
|
7
|
+
showIcon?: boolean
|
|
8
|
+
class?: HTMLAttributes['class']
|
|
9
|
+
}>()
|
|
10
|
+
|
|
11
|
+
const width = computed(() => {
|
|
12
|
+
return `${Math.floor(Math.random() * 40) + 50}%`;
|
|
13
|
+
})
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<div
|
|
18
|
+
data-sidebar="menu-skeleton"
|
|
19
|
+
:class="cn('rounded-md h-8 flex gap-2 px-2 items-center', props.class)"
|
|
20
|
+
>
|
|
21
|
+
<Skeleton
|
|
22
|
+
v-if="showIcon"
|
|
23
|
+
class="size-4 rounded-md"
|
|
24
|
+
data-sidebar="menu-skeleton-icon"
|
|
25
|
+
/>
|
|
26
|
+
|
|
27
|
+
<Skeleton
|
|
28
|
+
class="h-4 flex-1 max-w-[--skeleton-width]"
|
|
29
|
+
data-sidebar="menu-skeleton-text"
|
|
30
|
+
:style="{ '--skeleton-width': width }"
|
|
31
|
+
/>
|
|
32
|
+
</div>
|
|
33
|
+
</template>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'vue'
|
|
3
|
+
import { cn } from '@/lib/utils'
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
class?: HTMLAttributes['class']
|
|
7
|
+
}>()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<ul
|
|
12
|
+
data-sidebar="menu-badge"
|
|
13
|
+
:class="cn(
|
|
14
|
+
'mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5',
|
|
15
|
+
'group-data-[collapsible=icon]:hidden',
|
|
16
|
+
props.class,
|
|
17
|
+
)"
|
|
18
|
+
>
|
|
19
|
+
<slot />
|
|
20
|
+
</ul>
|
|
21
|
+
</template>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { PrimitiveProps } from 'radix-vue'
|
|
3
|
+
import type { HTMLAttributes } from 'vue'
|
|
4
|
+
import { cn } from '@/lib/utils'
|
|
5
|
+
import { Primitive } from 'radix-vue'
|
|
6
|
+
|
|
7
|
+
const props = withDefaults(defineProps<PrimitiveProps & {
|
|
8
|
+
size?: 'sm' | 'md'
|
|
9
|
+
isActive?: boolean
|
|
10
|
+
class?: HTMLAttributes['class']
|
|
11
|
+
}>(), {
|
|
12
|
+
as: 'a',
|
|
13
|
+
size: 'md',
|
|
14
|
+
})
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<Primitive
|
|
19
|
+
data-sidebar="menu-sub-button"
|
|
20
|
+
:as="as"
|
|
21
|
+
:as-child="asChild"
|
|
22
|
+
:data-size="size"
|
|
23
|
+
:data-active="isActive"
|
|
24
|
+
:class="cn(
|
|
25
|
+
'flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground',
|
|
26
|
+
'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
|
|
27
|
+
size === 'sm' && 'text-xs',
|
|
28
|
+
size === 'md' && 'text-sm',
|
|
29
|
+
'group-data-[collapsible=icon]:hidden',
|
|
30
|
+
props.class,
|
|
31
|
+
)"
|
|
32
|
+
>
|
|
33
|
+
<slot />
|
|
34
|
+
</Primitive>
|
|
35
|
+
</template>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { cn } from '@/lib/utils'
|
|
3
|
+
import { useEventListener, useMediaQuery, useVModel } from '@vueuse/core'
|
|
4
|
+
import { TooltipProvider } from 'radix-vue'
|
|
5
|
+
import { computed, type HTMLAttributes, type Ref, ref } from 'vue'
|
|
6
|
+
import { provideSidebarContext, SIDEBAR_COOKIE_MAX_AGE, SIDEBAR_COOKIE_NAME, SIDEBAR_KEYBOARD_SHORTCUT, SIDEBAR_WIDTH, SIDEBAR_WIDTH_ICON } from './utils'
|
|
7
|
+
|
|
8
|
+
const props = withDefaults(defineProps<{
|
|
9
|
+
defaultOpen?: boolean
|
|
10
|
+
open?: boolean
|
|
11
|
+
class?: HTMLAttributes['class']
|
|
12
|
+
}>(), {
|
|
13
|
+
defaultOpen: true,
|
|
14
|
+
open: undefined,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const emits = defineEmits<{
|
|
18
|
+
'update:open': [open: boolean]
|
|
19
|
+
}>()
|
|
20
|
+
|
|
21
|
+
const isMobile = useMediaQuery('(max-width: 768px)')
|
|
22
|
+
const openMobile = ref(false)
|
|
23
|
+
|
|
24
|
+
const open = useVModel(props, 'open', emits, {
|
|
25
|
+
defaultValue: props.defaultOpen ?? false,
|
|
26
|
+
passive: (props.open === undefined) as false,
|
|
27
|
+
}) as Ref<boolean>
|
|
28
|
+
|
|
29
|
+
function setOpen(value: boolean) {
|
|
30
|
+
open.value = value // emits('update:open', value)
|
|
31
|
+
|
|
32
|
+
// This sets the cookie to keep the sidebar state.
|
|
33
|
+
document.cookie = `${SIDEBAR_COOKIE_NAME}=${open.value}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function setOpenMobile(value: boolean) {
|
|
37
|
+
openMobile.value = value
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Helper to toggle the sidebar.
|
|
41
|
+
function toggleSidebar() {
|
|
42
|
+
return isMobile.value ? setOpenMobile(!openMobile.value) : setOpen(!open.value)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
useEventListener('keydown', (event: KeyboardEvent) => {
|
|
46
|
+
if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
|
|
47
|
+
event.preventDefault()
|
|
48
|
+
toggleSidebar()
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// We add a state so that we can do data-state="expanded" or "collapsed".
|
|
53
|
+
// This makes it easier to style the sidebar with Tailwind classes.
|
|
54
|
+
const state = computed(() => open.value ? 'expanded' : 'collapsed')
|
|
55
|
+
|
|
56
|
+
provideSidebarContext({
|
|
57
|
+
state,
|
|
58
|
+
open,
|
|
59
|
+
setOpen,
|
|
60
|
+
isMobile,
|
|
61
|
+
openMobile,
|
|
62
|
+
setOpenMobile,
|
|
63
|
+
toggleSidebar,
|
|
64
|
+
})
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<template>
|
|
68
|
+
<TooltipProvider :delay-duration="0">
|
|
69
|
+
<div
|
|
70
|
+
:style="{
|
|
71
|
+
'--sidebar-width': SIDEBAR_WIDTH,
|
|
72
|
+
'--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
|
|
73
|
+
}"
|
|
74
|
+
:class="cn('group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar', props.class)"
|
|
75
|
+
v-bind="$attrs"
|
|
76
|
+
>
|
|
77
|
+
<slot />
|
|
78
|
+
</div>
|
|
79
|
+
</TooltipProvider>
|
|
80
|
+
</template>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'vue'
|
|
3
|
+
import { cn } from '@/lib/utils'
|
|
4
|
+
import { useSidebar } from './utils'
|
|
5
|
+
|
|
6
|
+
const props = defineProps<{
|
|
7
|
+
class?: HTMLAttributes['class']
|
|
8
|
+
}>()
|
|
9
|
+
|
|
10
|
+
const { toggleSidebar } = useSidebar()
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<template>
|
|
14
|
+
<button
|
|
15
|
+
data-sidebar="rail"
|
|
16
|
+
aria-label="Toggle Sidebar"
|
|
17
|
+
:tabindex="-1"
|
|
18
|
+
title="Toggle Sidebar"
|
|
19
|
+
:class="cn(
|
|
20
|
+
'absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex',
|
|
21
|
+
'[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize',
|
|
22
|
+
'[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',
|
|
23
|
+
'group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar',
|
|
24
|
+
'[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',
|
|
25
|
+
'[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',
|
|
26
|
+
props.class,
|
|
27
|
+
)"
|
|
28
|
+
@click="toggleSidebar"
|
|
29
|
+
>
|
|
30
|
+
<slot />
|
|
31
|
+
</button>
|
|
32
|
+
</template>
|