Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: collapsible sidebar #1101

Merged
merged 3 commits into from
Apr 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/helpers/site_config_mariadb.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@
"root_login": "root",
"root_password": "root",
"host_name": "http://test_site:8000",
"install_apps": ["frappedesk"],
"install_apps": ["helpdesk"],
"throttle_user_limit": 100
}
20 changes: 20 additions & 0 deletions desk/src/components/desk/sidebar/LinkGroup.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<template>
<div class="flex flex-col gap-1">
<SidebarLink
v-for="option in options"
v-bind="option"
:key="option.label"
/>
</div>
</template>

<script setup lang="ts">
import SidebarLink from "./SidebarLink.vue";

defineProps({
options: {
type: Array,
required: true,
},
});
</script>
Original file line number Diff line number Diff line change
@@ -1,81 +1,15 @@
<template>
<div
class="flex w-56 shrink select-none flex-col border-r text-base text-gray-700"
class="flex select-none flex-col border-r p-2 text-base transition-all duration-300 ease-in-out"
:class="{
'w-56': sidebarStore.isExpanded,
'w-12': !sidebarStore.isExpanded,
}"
>
<div>
<Dropdown class="p-2" :options="profileSettings">
<template #default="{ open }">
<Button
appearance="minimal"
class="py-2 pl-2"
:label="authStore.userName"
:icon-right="open ? 'chevron-up' : 'chevron-down'"
>
<template #icon-left>
<Avatar
size="sm"
class="mr-2"
:label="authStore.userName"
:image-u-r-l="authStore.userImage"
/>
</template>
</Button>
</template>
</Dropdown>
</div>
<div class="flex flex-col gap-1 px-2">
<div
v-for="option in menuOptions"
:key="option.label"
class="flex cursor-pointer items-center gap-2 rounded-lg px-2 py-1"
:class="{
'bg-gray-200': isActive(option.label),
'text-gray-900': isActive(option.label),
'hover:bg-gray-300': isActive(option.label),
'hover:bg-gray-100': !isActive(option.label),
}"
@click="$router.push(option.to)"
>
<component
:is="option.icon"
v-show="!isActive(option.label)"
></component>
<component
:is="option.iconActive"
v-show="isActive(option.label)"
></component>
<div>
{{ option.label }}
</div>
</div>
</div>
<UserMenu class="pb-2" :options="profileSettings" />
<LinkGroup :options="menuOptions" />
<div class="grow"></div>
<div class="mb-3 flex flex-col gap-1 px-2">
<div
v-for="option in footerOptions"
:key="option.label"
class="flex cursor-pointer items-center gap-2 rounded-lg px-2 py-1"
:class="{
'bg-gray-200': isActive(option.label),
'text-gray-900': isActive(option.label),
'hover:bg-gray-300': isActive(option.label),
'hover:bg-gray-100': !isActive(option.label),
}"
@click="$router.push(option.to)"
>
<component
:is="option.icon"
v-show="!isActive(option.label)"
></component>
<component
:is="option.iconActive"
v-show="isActive(option.label)"
></component>
<div>
{{ option.label }}
</div>
</div>
</div>
<LinkGroup :options="footerOptions" />
<Dialog
v-model="showKeyboardShortcuts"
:options="{ title: 'Keyboard Shortcuts' }"
Expand Down Expand Up @@ -108,9 +42,10 @@

<script setup lang="ts">
import { ref } from "vue";
import { useRoute } from "vue-router";
import { Dropdown, Avatar } from "frappe-ui";
import { useAuthStore } from "@/stores/auth";
import { useSidebarStore } from "@/stores/sidebar";
import UserMenu from "./UserMenu.vue";
import LinkGroup from "./LinkGroup.vue";
import IconDashboard from "@/assets/icons/dashboard.svg?component";
import IconDashboardSolid from "@/assets/icons/dashboard-solid.svg?component";
import IconTicket from "@/assets/icons/ticket.svg?component";
Expand All @@ -124,8 +59,8 @@ import IconKnowledgeBaseSolid from "@/assets/icons/knowledge-base-solid.svg?comp
import IconSettings from "@/assets/icons/settings.svg?component";
import IconSettingsSolid from "@/assets/icons/settings-solid.svg?component";

const route = useRoute();
const authStore = useAuthStore();
const sidebarStore = useSidebarStore();
const isMac = navigator.userAgent.indexOf("Mac OS X") != -1;
const showKeyboardShortcuts = ref(false);

Expand All @@ -149,33 +84,25 @@ const menuOptions = [
label: "Dashboard",
icon: IconDashboard,
iconActive: IconDashboardSolid,
to: {
name: "Dashboard",
},
to: "Dashboard",
},
{
label: "Tickets",
icon: IconTicket,
iconActive: IconTicketSolid,
to: {
name: "DeskTickets",
},
to: "DeskTickets",
},
{
label: "Customers",
icon: IconCustomer,
iconActive: IconCustomerSolid,
to: {
name: "Customers",
},
to: "Customers",
},
{
label: "Contacts",
icon: IconContact,
iconActive: IconContactSolid,
to: {
name: "Contacts",
},
to: "Contacts",
},
];

Expand All @@ -184,17 +111,13 @@ const footerOptions = [
label: "Knowledge Base",
icon: IconKnowledgeBase,
iconActive: IconKnowledgeBaseSolid,
to: {
name: "DeskKBHome",
},
to: "DeskKBHome",
},
{
label: "Settings",
icon: IconSettings,
iconActive: IconSettingsSolid,
to: {
name: "Settings",
},
to: "Settings",
},
];

Expand All @@ -219,18 +142,4 @@ const profileSettings = [
handler: () => authStore.logout(),
},
];

const routeMap = {
"Knowledge Base": "/kb",
Contacts: "/contacts",
Customers: "/customers",
Dashboard: "/dashboard",
Reports: "/reports",
Settings: "/settings",
Tickets: "/tickets",
};

function isActive(label: string) {
return route.path.includes(routeMap[label]);
}
</script>
63 changes: 63 additions & 0 deletions desk/src/components/desk/sidebar/SidebarLink.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<template>
<router-link :to="{ name: to }">
<div
class="flex h-7 cursor-pointer items-center rounded-lg px-1.5 text-gray-700 transition-all duration-300 ease-in-out"
:class="{
'w-full': sidebarStore.isExpanded,
'w-7': !sidebarStore.isExpanded,
'bg-gray-200': isActive,
'text-gray-900': isActive,
'hover:bg-gray-300': isActive,
'hover:bg-gray-100': !isActive,
}"
>
<component :is="icon" v-show="!isActive" class="shrink-0"></component>
<component
:is="iconActive"
v-show="isActive"
class="shrink-0"
></component>
<div
class="ml-2 shrink-0 transition-all duration-300 ease-in-out"
:class="{
'opacity-100': sidebarStore.isExpanded,
'opacity-0': !sidebarStore.isExpanded,
'-z-50': !sidebarStore.isExpanded,
}"
>
{{ label }}
</div>
</div>
</router-link>
</template>

<script setup lang="ts">
import { computed, toRefs } from "vue";
import { useRoute } from "vue-router";
import { useSidebarStore } from "@/stores/sidebar";

const props = defineProps({
label: {
type: String,
required: true,
},
to: {
type: String,
required: true,
},
icon: {
type: Object,
required: true,
},
iconActive: {
type: Object,
required: false,
default: null,
},
});

const { to } = toRefs(props);
const route = useRoute();
const sidebarStore = useSidebarStore();
const isActive = computed(() => to.value.includes(route.name.toString()));
</script>
50 changes: 50 additions & 0 deletions desk/src/components/desk/sidebar/UserMenu.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<template>
<Dropdown :options="options">
<template #default="{ open }">
<div
class="flex w-max cursor-pointer items-center gap-2 rounded-md px-1 py-2 transition-all"
:class="{
'hover:bg-gray-100': sidebarStore.isExpanded,
}"
>
<Avatar
size="sm"
:label="authStore.userName"
:image-u-r-l="authStore.userImage"
/>
<div
class="flex shrink-0 items-center gap-1 duration-300"
:class="{
'opacity-100': sidebarStore.isExpanded,
'opacity-0': !sidebarStore.isExpanded,
'-z-50': !sidebarStore.isExpanded,
}"
>
<div>
{{ authStore.userName }}
</div>
<FeatherIcon
:name="open ? 'chevron-up' : 'chevron-down'"
class="h-4 w-4"
/>
</div>
</div>
</template>
</Dropdown>
</template>

<script setup lang="ts">
import { Avatar, Dropdown, FeatherIcon } from "frappe-ui";
import { useAuthStore } from "@/stores/auth";
import { useSidebarStore } from "@/stores/sidebar";

defineProps({
options: {
type: Array,
required: true,
},
});

const authStore = useAuthStore();
const sidebarStore = useSidebarStore();
</script>
10 changes: 5 additions & 5 deletions desk/src/pages/desk/Desk.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
>
<div v-if="initialized">
<div class="flex h-screen w-screen flex-row">
<SideBarMenu/>
<router-view :key="$route.fullPath" class="grow" />
<SideBar />
<router-view :key="$route.fullPath" class="z-0 grow" />
</div>
</div>
<div v-else class="flex h-full w-full max-w-full grow-0">
Expand All @@ -18,15 +18,15 @@
</template>

<script>
import { inject, provide, ref } from "vue";
import SideBarMenu from "@/components/desk/SideBarMenu.vue";
import { provide, ref } from "vue";
import SideBar from "@/components/desk/sidebar/SideBar.vue";
import CustomIcons from "@/components/desk/global/CustomIcons.vue";
import { useAuthStore } from "@/stores/auth";

export default {
name: "Desk",
components: {
SideBarMenu,
SideBar,
CustomIcons,
},
setup() {
Expand Down
35 changes: 35 additions & 0 deletions desk/src/stores/sidebar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { onMounted, ref, watch } from "vue";
import { useRoute } from "vue-router";
import { defineStore } from "pinia";

const MINIMISE_ON = ["DeskTicket", "Settings"];

export const useSidebarStore = defineStore("sidebar", () => {
const route = useRoute();
const isOpen = ref(true);
const isExpanded = ref(true);

function toggle(state?: boolean) {
isOpen.value = state ?? !isOpen.value;
}

function toggleExpanded(state?: boolean) {
isExpanded.value = state ?? !isExpanded.value;
}

function setMinimised() {
toggleExpanded(
!route.matched.find((r) => MINIMISE_ON.includes(r.name.toString()))
);
}

onMounted(setMinimised);
watch(route, setMinimised);

return {
isOpen,
isExpanded,
toggle,
toggleExpanded,
};
});