Browse Source

[frontend] Fix positioning of the DropdownMenu component when opened near the lower edge of the window

Johan Ahlen 4 years ago
parent
commit
ee6c649864

+ 1 - 0
desktop/core/src/desktop/js/components/__snapshots__/DateRangePicker.test.ts.snap

@@ -10,6 +10,7 @@ exports[`DateRangePicker.vue should render 1`] = `
   />
   <div
     class="hue-dropdown-drawer"
+    style="position: fixed;"
   >
     <!--v-if-->
   </div>

+ 82 - 0
desktop/core/src/desktop/js/components/dropdown/DropdownDrawer.scss

@@ -0,0 +1,82 @@
+/*
+ Licensed to Cloudera, Inc. under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  Cloudera, Inc. licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+@import '../styles/colors';
+@import '../styles/mixins';
+
+.hue-dropdown-drawer {
+  position: fixed;
+  z-index: 10610;
+
+  &.open {
+    .hue-dropdown-drawer-inner {
+      display: block;
+    }
+  }
+
+  .hue-dropdown-drawer-inner {
+    display: none;
+    position: absolute;
+    z-index: 1000;
+    margin: 2px 0 0;
+    padding: 0;
+    background-color: $fluid-white;
+    border: 1px solid $hue-border-color;
+    border-radius: $hue-panel-border-radius;
+
+    @include box-shadow(0, 5px, 10px, rgba(0, 0, 0, 0.2));
+
+    > ul {
+      overflow-x: hidden;
+      margin: 0 !important;
+      padding: 0;
+      list-style: none;
+      font-size: 13px;
+
+      li {
+        color: $fluid-gray-800;
+
+        button,
+        a {
+          display: block;
+          width: 100%;
+          padding: 6px 16px;
+          clear: both;
+          font-weight: 400;
+          text-align: inherit;
+          white-space: nowrap;
+          background-color: transparent;
+          border: 0;
+          position: relative;
+          outline: 0;
+
+          &:hover,
+          &.active,
+          &.focus {
+            background-color: $hue-primary-color-light;
+          }
+        }
+
+        &.selected button,
+        &.selected a {
+          background-color: $hue-primary-color-light;
+        }
+      }
+    }
+  }
+}

+ 15 - 73
desktop/core/src/desktop/js/components/dropdown/DropdownDrawer.vue

@@ -17,7 +17,13 @@
 -->
 
 <template>
-  <div class="hue-dropdown-drawer" :class="{ open: open }" @click="closeOnClick && closeDrawer()">
+  <div
+    ref="outerPanelElement"
+    class="hue-dropdown-drawer"
+    :class="{ open: open }"
+    :style="parentPosition"
+    @click="closeOnClick && closeDrawer()"
+  >
     <div v-if="open" ref="innerPanelElement" class="hue-dropdown-drawer-inner" :style="position">
       <slot />
     </div>
@@ -27,6 +33,7 @@
 <script lang="ts">
   import { defineComponent, nextTick, PropType } from 'vue';
 
+  import './DropdownDrawer.scss';
   import {
     addClickOutsideHandler,
     removeClickOutsideHandler
@@ -42,6 +49,7 @@
   };
 
   type PositionStyle = Pick<CSSStyleDeclaration, 'left' | 'right' | 'top' | 'bottom'>;
+  type ParentPositionStyle = Pick<CSSStyleDeclaration, 'position'>;
 
   const getDefaultPosition = (): PositionStyle => ({
     top: '100%',
@@ -71,7 +79,8 @@
       return {
         positionTop: false,
         positionLeft: false,
-        position: getDefaultPosition()
+        position: getDefaultPosition(),
+        parentPosition: { position: 'fixed' } as ParentPositionStyle
       };
     },
     watch: {
@@ -82,14 +91,14 @@
         }
 
         this.position = getDefaultPosition();
+        this.parentPosition = { position: 'fixed' };
         await nextTick();
 
-        if (!this.$refs.innerPanelElement) {
-          return;
-        }
         const { bottom, right } = isOutsideViewport(<HTMLElement>this.$refs.innerPanelElement);
         if (bottom) {
-          const bottomOffset = this.triggerElement ? this.triggerElement.offsetHeight : 0;
+          const bottomOffset = this.triggerElement?.offsetHeight || 0;
+          // position: relative Prevents fixed element from appearing below the window when at the bottom of the page
+          this.parentPosition = { position: 'relative' };
           this.position.top = '';
           this.position.bottom = `${bottomOffset}px`;
         }
@@ -119,70 +128,3 @@
     }
   });
 </script>
-
-<style lang="scss" scoped>
-  @import '../styles/colors';
-  @import '../styles/mixins';
-
-  .hue-dropdown-drawer {
-    position: fixed;
-    z-index: 10610;
-
-    &.open {
-      .hue-dropdown-drawer-inner {
-        display: block;
-      }
-    }
-
-    .hue-dropdown-drawer-inner {
-      display: none;
-      position: absolute;
-      z-index: 1000;
-      margin: 2px 0 0;
-      padding: 0;
-      background-color: $fluid-white;
-      border: 1px solid $hue-border-color;
-      border-radius: $hue-panel-border-radius;
-
-      @include box-shadow(0, 5px, 10px, rgba(0, 0, 0, 0.2));
-
-      > ::v-deep(ul) {
-        overflow-x: hidden;
-        margin: 0 !important;
-        padding: 0;
-        list-style: none;
-        font-size: 13px;
-
-        li {
-          color: $fluid-gray-800;
-
-          button,
-          a {
-            display: block;
-            width: 100%;
-            padding: 6px 16px;
-            clear: both;
-            font-weight: 400;
-            text-align: inherit;
-            white-space: nowrap;
-            background-color: transparent;
-            border: 0;
-            position: relative;
-            outline: 0;
-
-            &:hover,
-            &.active,
-            &.focus {
-              background-color: $hue-primary-color-light;
-            }
-          }
-
-          &.selected button,
-          &.selected a {
-            background-color: $hue-primary-color-light;
-          }
-        }
-      }
-    }
-  }
-</style>

+ 31 - 0
desktop/core/src/desktop/js/components/dropdown/DropdownMenu.scss

@@ -0,0 +1,31 @@
+/*
+ Licensed to Cloudera, Inc. under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  Cloudera, Inc. licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+@import '../styles/colors';
+
+.hue-dropdown-menu {
+  .hue-dropdown-menu-inner {
+    min-width: 160px;
+    max-width: 250px;
+    min-height: 34px;
+    max-height: 200px;
+
+    overflow-x: hidden;
+    overflow-y: auto;
+  }
+}

+ 5 - 23
desktop/core/src/desktop/js/components/dropdown/DropdownMenu.vue

@@ -17,18 +17,17 @@
 -->
 
 <template>
-  <dropdown-panel class="hue-dropdown-menu" :text="text" :link="link">
-    <div class="hue-dropdown-menu-inner">
-      <ul>
-        <slot />
-      </ul>
-    </div>
+  <dropdown-panel :text="text" :link="link">
+    <ul>
+      <slot />
+    </ul>
   </dropdown-panel>
 </template>
 
 <script lang="ts">
   import { defineComponent } from 'vue';
 
+  import './DropdownMenu.scss';
   import DropdownPanel from './DropdownPanel.vue';
 
   export default defineComponent({
@@ -36,7 +35,6 @@
     components: {
       DropdownPanel
     },
-
     props: {
       text: {
         type: String,
@@ -51,19 +49,3 @@
     }
   });
 </script>
-
-<style lang="scss" scoped>
-  @import '../styles/colors';
-
-  .hue-dropdown-menu {
-    .hue-dropdown-menu-inner {
-      min-width: 160px;
-      max-width: 250px;
-      min-height: 34px;
-      max-height: 200px;
-
-      overflow-x: hidden;
-      overflow-y: auto;
-    }
-  }
-</style>

+ 21 - 0
desktop/core/src/desktop/js/components/dropdown/DropdownPanel.scss

@@ -0,0 +1,21 @@
+/*
+ Licensed to Cloudera, Inc. under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  Cloudera, Inc. licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+.hue-dropdown-panel {
+  display: inline-block;
+}

+ 32 - 28
desktop/core/src/desktop/js/components/dropdown/DropdownPanel.vue

@@ -17,17 +17,17 @@
 -->
 
 <template>
-  <div class="hue-dropdown-panel">
-    <hue-link v-if="link" ref="triggerLinkElement" :disabled="disabled" @click="toggleDrawer">
+  <div ref="panelElement" class="hue-dropdown-panel">
+    <HueLink v-if="link" :disabled="disabled" @click="toggleDrawer">
       {{ text }} <i class="fa fa-caret-down" />
-    </hue-link>
-    <hue-button v-else ref="triggerButtonElement" :disabled="disabled" @click="toggleDrawer">
+    </HueLink>
+    <HueButton v-else :disabled="disabled" @click="toggleDrawer">
       {{ text }} <i class="fa fa-caret-down" />
-    </hue-button>
+    </HueButton>
     <DropdownDrawer
       :open="open"
       :trigger-element="triggerElement"
-      :close-on-click="false"
+      :close-on-click="true"
       @close="closeDrawer"
     >
       <slot :close-panel="closeDrawer" />
@@ -36,8 +36,9 @@
 </template>
 
 <script lang="ts">
-  import { defineComponent } from 'vue';
+  import { defineComponent, onMounted, ref } from 'vue';
 
+  import './DropdownPanel.scss';
   import DropdownDrawer from './DropdownDrawer.vue';
   import HueButton from '../HueButton.vue';
   import HueLink from '../HueLink.vue';
@@ -63,29 +64,32 @@
         default: false
       }
     },
-    data() {
+    setup() {
+      const panelElement = ref<HTMLElement | undefined>(undefined);
+      const triggerElement = ref<HTMLElement | undefined>(undefined);
+      const open = ref(false);
+
+      onMounted(() => {
+        if (panelElement.value?.firstChild) {
+          triggerElement.value = panelElement.value.firstChild as HTMLElement;
+        }
+      });
+
+      const toggleDrawer = () => {
+        open.value = !open.value;
+      };
+
+      const closeDrawer = () => {
+        open.value = false;
+      };
+
       return {
-        open: false,
-        triggerElement: null as HTMLElement | null
+        closeDrawer,
+        open,
+        panelElement,
+        toggleDrawer,
+        triggerElement
       };
-    },
-    mounted() {
-      this.triggerElement =
-        <HTMLElement>(this.$refs.triggerLinkElement || this.$refs.triggerButtonElement) || null;
-    },
-    methods: {
-      toggleDrawer(): void {
-        this.open = !this.open;
-      },
-      closeDrawer(): void {
-        this.open = false;
-      }
     }
   });
 </script>
-
-<style lang="scss" scoped>
-  .hue-dropdown-panel {
-    display: inline-block;
-  }
-</style>

+ 2 - 0
desktop/core/src/desktop/js/components/dropdown/__snapshots__/DropdownDrawer.test.ts.snap

@@ -3,6 +3,7 @@
 exports[`DropdownDrawer.vue should render dropdown drawer with slots 1`] = `
 <div
   class="hue-dropdown-drawer"
+  style="position: fixed;"
 >
   <!--v-if-->
 </div>
@@ -11,6 +12,7 @@ exports[`DropdownDrawer.vue should render dropdown drawer with slots 1`] = `
 exports[`DropdownDrawer.vue should render empty dropdown drawer 1`] = `
 <div
   class="hue-dropdown-drawer"
+  style="position: fixed;"
 >
   <!--v-if-->
 </div>

+ 4 - 2
desktop/core/src/desktop/js/components/dropdown/__snapshots__/DropdownMenu.test.ts.snap

@@ -2,13 +2,14 @@
 
 exports[`DropdownMenu.vue should render dropdown with slots 1`] = `
 <div
-  class="hue-dropdown-panel hue-dropdown-menu"
+  class="hue-dropdown-panel"
 >
   <hue-button-stub
     disabled="false"
   />
   <div
     class="hue-dropdown-drawer"
+    style="position: fixed;"
   >
     <!--v-if-->
   </div>
@@ -17,13 +18,14 @@ exports[`DropdownMenu.vue should render dropdown with slots 1`] = `
 
 exports[`DropdownMenu.vue should render empty dropdown 1`] = `
 <div
-  class="hue-dropdown-panel hue-dropdown-menu"
+  class="hue-dropdown-panel"
 >
   <hue-button-stub
     disabled="false"
   />
   <div
     class="hue-dropdown-drawer"
+    style="position: fixed;"
   >
     <!--v-if-->
   </div>

+ 2 - 0
desktop/core/src/desktop/js/components/dropdown/__snapshots__/DropdownPanel.test.ts.snap

@@ -9,6 +9,7 @@ exports[`DropdownPanel.vue should render dropdown panel with slots 1`] = `
   />
   <div
     class="hue-dropdown-drawer"
+    style="position: fixed;"
   >
     <!--v-if-->
   </div>
@@ -24,6 +25,7 @@ exports[`DropdownPanel.vue should render empty dropdown panel 1`] = `
   />
   <div
     class="hue-dropdown-drawer"
+    style="position: fixed;"
   >
     <!--v-if-->
   </div>