LayoutContent.vue 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. <!-- Copyright (C) 2012-2025 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. * Removes padding from main container
  35. * Applies padding to breadcrumb container
  36. * ⚠️ Set manually the padding to the default slot container p-4
  37. */
  38. contentPadding?: boolean
  39. /**
  40. * Disables the vertical scroll on the main element
  41. */
  42. noScrollable?: boolean
  43. }
  44. const props = withDefaults(defineProps<Props>(), {
  45. name: 'content',
  46. backgroundVariant: 'tertiary',
  47. width: 'full',
  48. showInlineHelp: false,
  49. contentAlignment: 'start',
  50. })
  51. const maxWidth = computed(() =>
  52. props.width === 'narrow' ? '600px' : undefined,
  53. )
  54. const contentAlignmentClass = computed(() => {
  55. return props.contentAlignment === 'center' ? 'items-center' : ''
  56. })
  57. const noTransition = ref(false)
  58. const { userId } = useSessionStore()
  59. const storageKeyId = `${userId}-${props.name}`
  60. const {
  61. currentSidebarWidth,
  62. maxSidebarWidth,
  63. minSidebarWidth,
  64. gridColumns,
  65. collapseSidebar,
  66. expandSidebar,
  67. resizeSidebar,
  68. resetSidebarWidth,
  69. } = useResizeGridColumns(storageKeyId, SidebarPosition.End)
  70. const { durations } = useTransitionConfig()
  71. </script>
  72. <template>
  73. <div class="flex h-full max-h-screen flex-col">
  74. <div
  75. class="grid h-full duration-100"
  76. :class="{
  77. 'transition-none': noTransition,
  78. 'max-h-[calc(100%-3.5rem)]': $slots.bottomBar,
  79. 'max-h-screen': !$slots.bottomBar,
  80. }"
  81. :style="$slots.sideBar && showSidebar ? gridColumns : undefined"
  82. >
  83. <LayoutMain
  84. ref="layout-main"
  85. :no-padding="noPadding || contentPadding"
  86. :no-scrollable="noScrollable"
  87. :background-variant="backgroundVariant"
  88. >
  89. <div
  90. data-test-id="layout-wrapper"
  91. class="flex h-full grow flex-col gap-3"
  92. :class="contentAlignmentClass"
  93. :style="{ maxWidth }"
  94. >
  95. <div
  96. v-if="breadcrumbItems"
  97. data-test-id="wrapper-breadcrumb"
  98. class="flex items-center justify-between"
  99. :class="{ 'px-4 pt-4': contentPadding }"
  100. >
  101. <CommonBreadcrumb :items="breadcrumbItems" />
  102. <div
  103. v-if="$slots.headerRight || helpText || $slots.helpPage"
  104. class="flex gap-4 ltr:text-left rtl:text-right"
  105. >
  106. <CommonPageHelp
  107. v-if="!showInlineHelp && (helpText || $slots.helpPage)"
  108. >
  109. <slot name="helpPage">
  110. <CommonHelpText :help-text="helpText" />
  111. </slot>
  112. </CommonPageHelp>
  113. <slot name="headerRight" />
  114. </div>
  115. </div>
  116. <Transition :duration="durations.normal" name="fade" mode="out-in">
  117. <slot v-if="!showInlineHelp" />
  118. <slot v-else name="helpPage">
  119. <CommonHelpText :help-text="helpText" />
  120. </slot>
  121. </Transition>
  122. </div>
  123. </LayoutMain>
  124. <LayoutSidebar
  125. v-if="$slots.sideBar"
  126. v-show="showSidebar"
  127. id="content-sidebar"
  128. #default="{ isCollapsed, toggleCollapse }"
  129. :name="storageKeyId"
  130. :position="SidebarPosition.End"
  131. :aria-label="$t('Content sidebar')"
  132. collapsible
  133. resizable
  134. :current-width="currentSidebarWidth"
  135. :max-width="maxSidebarWidth"
  136. :min-width="minSidebarWidth"
  137. no-padding
  138. no-scroll
  139. class="bg-neutral-50 dark:bg-gray-500"
  140. :class="{
  141. 'max-h-[calc(100dvh-3.5rem)]': $slots.bottomBar,
  142. }"
  143. @collapse="collapseSidebar"
  144. @expand="expandSidebar"
  145. @resize-horizontal="resizeSidebar"
  146. @resize-horizontal-start="noTransition = true"
  147. @resize-horizontal-end="noTransition = false"
  148. @reset-width="resetSidebarWidth"
  149. >
  150. <slot name="sideBar" v-bind="{ isCollapsed, toggleCollapse }" />
  151. </LayoutSidebar>
  152. </div>
  153. <LayoutBottomBar v-if="$slots.bottomBar">
  154. <slot name="bottomBar" />
  155. </LayoutBottomBar>
  156. </div>
  157. </template>