LayoutContent.vue 4.8 KB

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