SidebarProvider.vue 2.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. <script setup lang="ts">
  2. import type { HTMLAttributes, Ref } from "vue"
  3. import { defaultDocument, useEventListener, useMediaQuery, useVModel } from "@vueuse/core"
  4. import { TooltipProvider } from "reka-ui"
  5. import { computed, ref } from "vue"
  6. import { cn } from '@/Packages/Shadcn/Lib/utils'
  7. import { provideSidebarContext, SIDEBAR_COOKIE_MAX_AGE, SIDEBAR_COOKIE_NAME, SIDEBAR_KEYBOARD_SHORTCUT, SIDEBAR_WIDTH, SIDEBAR_WIDTH_ICON } from "./utils"
  8. const props = withDefaults(defineProps<{
  9. defaultOpen?: boolean
  10. open?: boolean
  11. class?: HTMLAttributes["class"]
  12. }>(), {
  13. defaultOpen: !defaultDocument?.cookie.includes(`${SIDEBAR_COOKIE_NAME}=false`),
  14. open: undefined,
  15. })
  16. const emits = defineEmits<{
  17. "update:open": [open: boolean]
  18. }>()
  19. const isMobile = useMediaQuery("(max-width: 768px)")
  20. const openMobile = ref(false)
  21. const open = useVModel(props, "open", emits, {
  22. defaultValue: props.defaultOpen ?? false,
  23. passive: (props.open === undefined) as false,
  24. }) as Ref<boolean>
  25. function setOpen(value: boolean) {
  26. open.value = value // emits('update:open', value)
  27. // This sets the cookie to keep the sidebar state.
  28. document.cookie = `${SIDEBAR_COOKIE_NAME}=${open.value}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
  29. }
  30. function setOpenMobile(value: boolean) {
  31. openMobile.value = value
  32. }
  33. // Helper to toggle the sidebar.
  34. function toggleSidebar() {
  35. return isMobile.value ? setOpenMobile(!openMobile.value) : setOpen(!open.value)
  36. }
  37. useEventListener("keydown", (event: KeyboardEvent) => {
  38. if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
  39. event.preventDefault()
  40. toggleSidebar()
  41. }
  42. })
  43. // We add a state so that we can do data-state="expanded" or "collapsed".
  44. // This makes it easier to style the sidebar with Tailwind classes.
  45. const state = computed(() => open.value ? "expanded" : "collapsed")
  46. provideSidebarContext({
  47. state,
  48. open,
  49. setOpen,
  50. isMobile,
  51. openMobile,
  52. setOpenMobile,
  53. toggleSidebar,
  54. })
  55. </script>
  56. <template>
  57. <TooltipProvider :delay-duration="0">
  58. <div
  59. data-slot="sidebar-wrapper"
  60. :style="{
  61. '--sidebar-width': SIDEBAR_WIDTH,
  62. '--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
  63. }"
  64. :class="cn('group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full', props.class)"
  65. v-bind="$attrs"
  66. >
  67. <slot />
  68. </div>
  69. </TooltipProvider>
  70. </template>