0

I have a dashboard design I am working on in quasar that I am trying to get to flow correctly. When things squish together horizontally everything closes up smoothly and its very responses. My issue is if the user brings the screen up vertically on a item that is already selected in the sidebar then the highlighted item curves off into nothing and doesnt flow into the body of the q-card to look like its a single piece on the website. My screenshots are provideed below:

Design Okay Design Issue

My code is as follows for my layout and my sidebar component:

Mainlayout

<template>
  <q-layout view="hHh lpR fFf">
    <q-page-container class="bg-black q-pa-md" style-fn="minHeight: 0px">
      <div class="row">
        <div class="col" :style="sideBarStyles()">
          <SideNavigation/>
        </div>
        <div class="col">
          <q-card style="border-radius: 30px; height: calc(100vh - 32px); overflow: hidden;" flat>
            <TopNavigation/>
            <router-view />
          </q-card>
        </div>
      </div>
    </q-page-container>
  </q-layout>
</template>

<script setup>
  import { ref } from 'vue'
  import { storeToRefs } from 'pinia'
  import SideNavigation from 'components/SideNavigation.vue'
  import TopNavigation from 'components/TopNavigation.vue'
  import {useLayoutStore} from '../stores/layout-store'
  import { useQuasar } from 'quasar'

  const $q = useQuasar()
  const layoutStore = useLayoutStore()
  const {primaryNavigationDrawer} = storeToRefs(layoutStore)

  function sideBarStyles () {
    if(primaryNavigationDrawer.value){
      if($q.screen.xs){
        return 'max-width: 70px; transition: all .5s;'
      } else {
        return 'max-width: 100px; transition: all .5s;'
      }
    } else {
      if($q.screen.xs){
        return 'max-width: 0px; transition: all .5s;'
      } else {
        return 'max-width: 100px; transition: all .5s;'
      }
    }
  }
</script>
<style scoped>

</style>

Sidebar Component:

<template>
  <div style="height: calc(100vh - 32px); overflow: hidden;">
    <div class="window-height bg-black text-white text-center">
      <div class="q-pt-xl">
        <q-img
          src="~assets/img/sidebarLogo.png"
          spinner-color="white"
          style="max-width: 40px;"
        />
      </div>
      <div class="q-py-xl">
        <q-list class="curved-parent-ul">
          <q-item v-for="(menuItem, index) in primaryMenuArr" :key="index" class="curved-child-li"
            :active="activeMenuItem === menuItem.name"
            @click="activeMenuItem = menuItem.name"
            active-class="active"
            clickable
            manual-focus
            :link="menuItem.link"
            no-ripple>
            <q-icon size="sm" :name="menuItem.icon" class="absolute-center" />
          </q-item>
        </q-list>
      </div>
      <q-separator dark inset :size="'1.5px'" class="bg-white"/>
      <div class="q-py-xl">
        <q-list class="curved-parent-ul">
          <q-item v-for="(menuItem, index) in secondaryMenuArr" :key="index" class="curved-child-li"
            :active="activeMenuItem === menuItem.name"
            @click="activeMenuItem = menuItem.name"
            active-class="active"
            clickable
            manual-focus
            no-ripple>
            <q-icon size="sm" :name="menuItem.icon" class="absolute-center" />
          </q-item>
        </q-list>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
  import { ref } from 'vue'
  import { storeToRefs } from 'pinia'
  import {useLayoutStore} from '../stores/layout-store'

  const layoutStore = useLayoutStore()
  const {primaryNavigationDrawer} = storeToRefs(layoutStore)
  const leftDrawerOpen: Ref<boolean> = ref(true)
  const activeMenuItem: Ref<string> = ref('Home')

  interface menuArr {
    name: string;
    ariaLabel: string;
    icon: string;
    link: string;
    }
  const primaryMenuArr: menuArr[ ] = [
    { name: 'Home', ariaLabel: 'Home', icon: 's_dashboard', link: '#' },
    { name: 'Upload', ariaLabel: 'Upload', icon: 's_drive_folder_upload', link: '#' },
    { name: 'Management', ariaLabel: 'Management', icon: 's_work', link: '#' },
    ]

  const secondaryMenuArr: menuArr[ ] = [
    { name: 'Settings', ariaLabel: 'Settings', icon: 's_settings', link: '#' },
    { name: 'Log Off', ariaLabel: 'Upload', icon: 's_logout', link: '#' },
    ]
</script>
<style scoped>
.curved-parent-ul {
  padding: 20px 0px;
  width: 100%;
  transition: 0s;
  overflow: hidden;
}

.curved-parent-ul .curved-child-li {
  list-style: none;
  padding: 30px;
  padding-right: 0;
  color: white;
  font-size: 15px;
  margin-bottom: 30px;
  cursor: pointer;
  position: relative;
  transition: 0s;
  border-top-left-radius: 30px;
  border-bottom-left-radius: 30px;

}

.curved-parent-ul .curved-child-li.active:before {
  content: '';
  position: absolute;
  top:-30px;
  right: 0px;
  width: 30px;
  height:30px;
  border-radius: 50%;
  box-shadow: 15px 15px 0 #fff;
}

.curved-parent-ul .curved-child-li.active:after {
  content: '';
  position: absolute;
  bottom:-30px;
  right: 0px;
  width: 30px;
  height:30px;
  border-radius: 50%;
  box-shadow: 15px -15px 0 #fff;
}

.curved-parent-ul .curved-child-li.active {
  background: #fff;
  color: #000;
}
</style>

I am struggling as I am not sure how to handle this issue. I am also somewhat curious how one would handle making these menu items hoverable/focusable as well ( i set to manual-focus as I could not imitate the same effect of the active class to make it look okay).

Overall finalizing this dashboard style with quasar is difficult but I am sure i am missing something and it can be done!

My solution in my head which i am clueless how to achieve is the active item needs a rounded rectangle the width of the radius of the q-card border on the end where the q-card starts.

*Edit based off of feedback below. Here is what would happen if I increase padding for left menu to be raised. It is better than the original but overall still has to be something cleaner. enter image description here

Irish Redneck
  • 983
  • 7
  • 32

1 Answers1

1

Make a margin top/bottom or a padding top/bottom in left menu to not display too high or to low, those paddings will be your safe spaces to not show your items menu

2nd option: if the selected option is the first, the border-top-left-radius of the right container shoud be less, and the same at the bottom. If the option item is the last, then the border-bottom-right-radius should be less

Edit: demo:

const container = document.querySelector(".container");
const items = document.querySelectorAll(".menu .item");
const itemsLength = items.length;

for (let x = 0; x < itemsLength; x++) {
    items[x].addEventListener("click", () => {
    container.className = "container";
        items.forEach(item => item.className = "item"); //reset classes
        items[x].classList.add("selected");
    if (x === 0) {
        container.classList.add("first-item");
    }
    if (x + 1 === itemsLength) {
        container.classList.add("last-item");
    }
    if (x > 0) {
        items[x - 1].classList.add("selected-before");
    }
    if (x + 1 < itemsLength) {
        items[x + 1].classList.add("selected-after");
    }
    
  });
}
html,
body {
  margin: 0;
  width: 100%;
  height: 100%;
}

body {
  border: 10px solid black;
}

.container {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: stretch;
  align-items: stretch;
  background-color: black;
}

.menu {
  width: 60px;
  display: flex;
  flex-direction: column;
  justify-content: stretch;
  align-items: stretch;
background: linear-gradient(90deg, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 1) 49%, rgba(221, 221, 221, 1) 50%);
}

.menu .item {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 1 1 0;
  color: white;
  border-top-left-radius: 5px;
  border-bottom-left-radius: 5px;
  background-color: black;
}

.menu .item.selected {
  background-color: #ddd;
  color: black;
}

.menu .item.selected-before {
  border-bottom-right-radius: 5px;
}

.menu .item.selected-after {
  border-top-right-radius: 5px;
}

.content {
  width: 100%;
  display: flex;
  padding: 20px;
  border-radius: 5px;
  background-color: #ddd;
}

.container.first-item .content {
  border-top-left-radius: 0;
}

.container.last-item .content {
  border-bottom-left-radius: 0;
}
<div class="container">
  <div class="menu">
    <div class="item">A</div>
    <div class="item">B</div>
    <div class="item">C</div>
    <div class="item">D</div>
    <div class="item">E</div>
  </div>
  <div class="content">
    demo content
  </div>
</div>
  • Could you show an example via code? I would be curious to see what you mean exactly. – Irish Redneck May 11 '23 at 22:36
  • You did not left a editable environment, I could suggest code modifications in it – Cristóbal Díaz Álvarez May 11 '23 at 22:40
  • Am i able to setup a quasar vuejs test environment online? Never done that before. – Irish Redneck May 11 '23 at 23:17
  • Although I never got a testing environment setup I think your padding approach to keep the left menu higher is the best approach. I cannot think of anything else that looks good. So I am selecting this answer. Thank you – Irish Redneck May 16 '23 at 13:41
  • 1
    Anytime, I think with your example is the easiest way. Anyway, I was thinking about this and I figure out this method, it is pretty simply, hope it helps https://jsfiddle.net/Cristobal_Diaz_Alvarez/8c9nuv0j/47/ good luck – – Cristóbal Díaz Álvarez May 17 '23 at 03:51
  • I am digging your new solution actually. Does flex allow a minimum spacing to keep the proportions/distances of how i arrange my icons? Basically I have logo at top of sidebar, middle includes a group of icons which you are able to see in my example pic above, then at the bottom of sidebar is a logout icon. I pretty much do justify-content: space between for these three sections and once that collapses it just hides those (I dont use a sidebar since its ugly). But if they all can collapse in the manner you did that really is a vertically responsive menu. Again great idea! – Irish Redneck May 18 '23 at 15:53