LayoutContent.vue 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. <!-- Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. import { computed, ref } from 'vue'
  4. import { useTransitionConfig } from '#shared/composables/useTransitionConfig.ts'
  5. import { useSessionStore } from '#shared/stores/session.ts'
  6. import CommonBreadcrumb from '#desktop/components/CommonBreadcrumb/CommonBreadcrumb.vue'
  7. import type { BreadcrumbItem } from '#desktop/components/CommonBreadcrumb/types.ts'
  8. import CommonHelpText from '#desktop/components/CommonPageHelp/CommonHelpText.vue'
  9. import CommonPageHelp from '#desktop/components/CommonPageHelp/CommonPageHelp.vue'
  10. import LayoutBottomBar from '#desktop/components/layout/LayoutBottomBar.vue'
  11. import LayoutMain from '#desktop/components/layout/LayoutMain.vue'
  12. import LayoutSidebar from '#desktop/components/layout/LayoutSidebar.vue'
  13. import { useResizeGridColumns } from '#desktop/composables/useResizeGridColumns.ts'
  14. import {
  15. SidebarPosition,
  16. type BackgroundVariant,
  17. type ContentAlignment,
  18. type ContentWidth,
  19. } from './types.ts'
  20. export interface Props {
  21. name?: string
  22. breadcrumbItems?: BreadcrumbItem[]
  23. width?: ContentWidth
  24. backgroundVariant?: BackgroundVariant
  25. contentAlignment?: ContentAlignment
  26. helpText?: string[] | string
  27. /**
  28. * Hides `default slot` content and shows help text if provided
  29. */
  30. showInlineHelp?: boolean
  31. showSidebar?: boolean
  32. noPadding?: boolean
  33. /**
  34. * Disables the vertical scroll on the main element
  35. */
  36. noScrollable?: boolean
  37. }
  38. const props = withDefaults(defineProps<Props>(), {
  39. name: 'content',
  40. backgroundVariant: 'tertiary',
  41. width: 'full',
  42. showInlineHelp: false,
  43. contentAlignment: 'start',
  44. })
  45. const maxWidth = computed(() =>
  46. props.width === 'narrow' ? '600px' : undefined,
  47. )
  48. const contentAlignmentClass = computed(() => {
  49. return props.contentAlignment === 'center' ? 'items-center' : ''
  50. })
  51. const noTransition = ref(false)
  52. const { userId } = useSessionStore()
  53. const storageKeyId = `${userId}-${props.name}`
  54. const {
  55. currentSidebarWidth,
  56. maxSidebarWidth,
  57. minSidebarWidth,
  58. gridColumns,
  59. collapseSidebar,
  60. expandSidebar,
  61. resizeSidebar,
  62. resetSidebarWidth,
  63. } = useResizeGridColumns(storageKeyId, SidebarPosition.End)
  64. const { durations } = useTransitionConfig()
  65. </script>
  66. <template>
  67. <div class="flex h-full max-h-screen flex-col">
  68. <div
  69. class="grid h-full overflow-y-auto duration-100"
  70. :class="{
  71. 'transition-none': noTransition,
  72. 'max-h-[calc(100%-3.5rem)]': $slots.bottomBar,
  73. 'max-h-screen': !$slots.bottomBar,
  74. }"
  75. :style="$slots.sideBar && showSidebar ? gridColumns : undefined"
  76. >
  77. <LayoutMain
  78. :class="{ 'overflow-y-hidden': noScrollable }"
  79. :no-padding="noPadding"
  80. :background-variant="backgroundVariant"
  81. >
  82. <div
  83. data-test-id="layout-wrapper"
  84. class="flex h-full grow flex-col gap-3"
  85. :class="contentAlignmentClass"
  86. :style="{ maxWidth }"
  87. >
  88. <div v-if="breadcrumbItems" class="flex items-center justify-between">
  89. <CommonBreadcrumb :items="breadcrumbItems" />
  90. <div
  91. v-if="$slots.headerRight || helpText || $slots.helpPage"
  92. class="flex gap-4 ltr:text-left rtl:text-right"
  93. >
  94. <CommonPageHelp
  95. v-if="!showInlineHelp && (helpText || $slots.helpPage)"
  96. >
  97. <slot name="helpPage">
  98. <CommonHelpText :help-text="helpText" />
  99. </slot>
  100. </CommonPageHelp>
  101. <slot name="headerRight" />
  102. </div>
  103. </div>
  104. <Transition :duration="durations.normal" name="fade" mode="out-in">
  105. <slot v-if="!showInlineHelp" />
  106. <slot v-else name="helpPage">
  107. <CommonHelpText :help-text="helpText" />
  108. </slot>
  109. </Transition>
  110. </div>
  111. </LayoutMain>
  112. <LayoutSidebar
  113. v-if="$slots.sideBar"
  114. v-show="showSidebar"
  115. id="content-sidebar"
  116. #default="{ isCollapsed, toggleCollapse }"
  117. :name="storageKeyId"
  118. :position="SidebarPosition.End"
  119. :aria-label="$t('Content sidebar')"
  120. collapsible
  121. resizable
  122. :current-width="currentSidebarWidth"
  123. :max-width="maxSidebarWidth"
  124. :min-width="minSidebarWidth"
  125. no-padding
  126. no-scroll
  127. class="bg-neutral-50 dark:bg-gray-500"
  128. :class="{
  129. 'max-h-[calc(100dvh-3.5rem)]': $slots.bottomBar,
  130. }"
  131. @collapse="collapseSidebar"
  132. @expand="expandSidebar"
  133. @resize-horizontal="resizeSidebar"
  134. @resize-horizontal-start="noTransition = true"
  135. @resize-horizontal-end="noTransition = false"
  136. @reset-width="resetSidebarWidth"
  137. >
  138. <slot name="sideBar" v-bind="{ isCollapsed, toggleCollapse }" />
  139. </LayoutSidebar>
  140. </div>
  141. <LayoutBottomBar v-if="$slots.bottomBar">
  142. <slot name="bottomBar" />
  143. </LayoutBottomBar>
  144. </div>
  145. </template>