LayoutContent.vue 4.7 KB

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