LayoutContent.vue 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  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. ref="layout-main"
  79. :no-padding="noPadding"
  80. :no-scrollable="noScrollable"
  81. :background-variant="backgroundVariant"
  82. >
  83. <div
  84. data-test-id="layout-wrapper"
  85. class="flex h-full grow flex-col gap-3"
  86. :class="contentAlignmentClass"
  87. :style="{ maxWidth }"
  88. >
  89. <div v-if="breadcrumbItems" class="flex items-center justify-between">
  90. <CommonBreadcrumb :items="breadcrumbItems" />
  91. <div
  92. v-if="$slots.headerRight || helpText || $slots.helpPage"
  93. class="flex gap-4 ltr:text-left rtl:text-right"
  94. >
  95. <CommonPageHelp
  96. v-if="!showInlineHelp && (helpText || $slots.helpPage)"
  97. >
  98. <slot name="helpPage">
  99. <CommonHelpText :help-text="helpText" />
  100. </slot>
  101. </CommonPageHelp>
  102. <slot name="headerRight" />
  103. </div>
  104. </div>
  105. <Transition :duration="durations.normal" name="fade" mode="out-in">
  106. <slot v-if="!showInlineHelp" />
  107. <slot v-else name="helpPage">
  108. <CommonHelpText :help-text="helpText" />
  109. </slot>
  110. </Transition>
  111. </div>
  112. </LayoutMain>
  113. <LayoutSidebar
  114. v-if="$slots.sideBar"
  115. v-show="showSidebar"
  116. id="content-sidebar"
  117. #default="{ isCollapsed, toggleCollapse }"
  118. :name="storageKeyId"
  119. :position="SidebarPosition.End"
  120. :aria-label="$t('Content sidebar')"
  121. collapsible
  122. resizable
  123. :current-width="currentSidebarWidth"
  124. :max-width="maxSidebarWidth"
  125. :min-width="minSidebarWidth"
  126. no-padding
  127. no-scroll
  128. class="bg-neutral-50 dark:bg-gray-500"
  129. :class="{
  130. 'max-h-[calc(100dvh-3.5rem)]': $slots.bottomBar,
  131. }"
  132. @collapse="collapseSidebar"
  133. @expand="expandSidebar"
  134. @resize-horizontal="resizeSidebar"
  135. @resize-horizontal-start="noTransition = true"
  136. @resize-horizontal-end="noTransition = false"
  137. @reset-width="resetSidebarWidth"
  138. >
  139. <slot name="sideBar" v-bind="{ isCollapsed, toggleCollapse }" />
  140. </LayoutSidebar>
  141. </div>
  142. <LayoutBottomBar v-if="$slots.bottomBar">
  143. <slot name="bottomBar" />
  144. </LayoutBottomBar>
  145. </div>
  146. </template>