cssvue.jsquasar-frameworkdesignerui-design

How to use a curved menu approach with a q-card as the body


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


Solution

  • 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>