fix sidebar speed dial buttons

This commit is contained in:
Casey 2025-12-11 15:18:27 -06:00
parent 080d9ff1b4
commit a01f72bbc1
2 changed files with 166 additions and 52 deletions

View File

@ -1,5 +1,5 @@
<script setup>
import { ref } from "vue";
import { ref, nextTick } from "vue";
import { useRouter } from "vue-router";
import { useModalStore } from "@/stores/modal";
import { useNotificationStore } from "@/stores/notifications-primevue"
@ -19,17 +19,27 @@ import {
NavArrowLeft,
NavArrowRight,
} from "@iconoir/vue";
import SpeedDial from "primevue/speeddial";
import SidebarSpeedDial from "./SidebarSpeedDial.vue";
const router = useRouter();
const modalStore = useModalStore();
const notifications = useNotificationStore();
const isCollapsed = ref(false);
const pendingOpen = ref(null);
const toggleSidebar = () => {
isCollapsed.value = !isCollapsed.value;
};
const openSidebarAndDial = (category) => {
isCollapsed.value = false;
pendingOpen.value = category.name;
// ensure re-render picks up forceOpen
nextTick(() => {
pendingOpen.value = category.name;
});
};
const clientButtons = ref([
{
label: "Customer Lookup",
@ -89,7 +99,7 @@ const createButtons = ref([
},
},
{
lable: "Note",
label: "Note",
command: () => {
notifications.addWarning("Sending Notes coming soon!");
}
@ -100,7 +110,7 @@ const categories = ref([
{ name: "Home", icon: Home, url: "/" },
{ name: "Calendar", icon: Calendar, url: "/calendar" },
{
name: "Clients",
name: "CRM",
icon: Community,
buttons: clientButtons,
},
@ -150,30 +160,22 @@ const handleCategoryClick = (category) => {
</button>
</template>
<template v-else>
<SpeedDial
:model="category.buttons"
direction="down"
type="linear"
radius="50"
<SidebarSpeedDial
v-if="!isCollapsed"
>
<template #button="{ toggleCallback }">
<button
class="sidebar-button"
@click="toggleCallback"
:key="category.name"
>
<component :is="category.icon" class="button-icon" />
<span class="button-text">{{ category.name }}</span>
</button>
</template>
<template #item="{ item, toggleCallback }">
<button class="create-item" @click="toggleCallback" :key="item.label">
<span class="p-menuitem-text">{{ item.label }}</span>
</button>
</template>
</SpeedDial>
<button v-else class="sidebar-button" :key="category.name" :title="category.name">
:key="category.name"
:icon="category.icon"
:label="category.name"
:items="category.buttons"
:force-open="pendingOpen === category.name"
@opened="pendingOpen = null"
/>
<button
v-else
class="sidebar-button"
:key="category.name"
:title="category.name"
@click="openSidebarAndDial(category)"
>
<component :is="category.icon" class="button-icon" />
</button>
</template>
@ -217,22 +219,6 @@ const handleCategoryClick = (category) => {
color: black;
}
.create-item {
border: none;
background-color: transparent;
width: 100%;
text-align: left;
padding: 8px 12px;
cursor: pointer;
font-size: 0.85rem;
color: var(--text-color);
transition: background-color 0.2s ease;
}
.create-item:hover {
background-color: var(--surface-hover);
}
.button-text {
flex: 1;
text-align: left;
@ -263,6 +249,13 @@ const handleCategoryClick = (category) => {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}
.speeddial-caret {
margin-right: 10px;
font-weight: 700;
font-size: 1rem;
line-height: 1;
}
.sidebar-button:active {
transform: scale(0.97);
}
@ -338,21 +331,63 @@ const handleCategoryClick = (category) => {
}
/* SpeedDial customization */
:deep(.p-speeddial) {
position: relative;
.sidebar-speeddial {
display: flex;
flex-direction: column;
width: 100%;
}
:deep(.p-speeddial-list) {
.sidebar-submenu {
background-color: var(--surface-card);
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
border: 1px solid var(--surface-border);
padding: 4px;
margin-top: 2px;
border-radius: 6px;
margin-top: 6px;
padding: 6px 4px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
}
:deep(.p-speeddial-item) {
margin: 2px 0;
.sidebar-sub-button {
border: none;
background: transparent;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
padding: 8px 10px;
border-radius: 4px;
cursor: pointer;
font-size: 0.85rem;
color: var(--text-color);
transition: background-color 0.15s ease, transform 0.1s ease;
}
.sidebar-sub-button:hover {
background-color: var(--surface-hover);
transform: translateX(2px);
}
.sub-button-text {
padding-left: 0;
}
.sidebar-accordion-enter-active,
.sidebar-accordion-leave-active {
transition: max-height 0.25s ease, opacity 0.2s ease, margin 0.2s ease;
}
.sidebar-accordion-enter-from,
.sidebar-accordion-leave-to {
max-height: 0;
opacity: 0;
margin-top: 0;
}
.sidebar-accordion-enter-to,
.sidebar-accordion-leave-from {
max-height: 600px;
opacity: 1;
margin-top: 6px;
}
/* Responsive adjustments for smaller screens */

View File

@ -0,0 +1,79 @@
<script setup>
import { ref, watch } from "vue";
const props = defineProps({
icon: {
type: [Object, Function],
required: true,
},
label: {
type: String,
required: true,
},
items: {
type: Array,
default: () => [],
},
initiallyOpen: {
type: Boolean,
default: false,
},
forceOpen: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["opened", "closed"]);
const isOpen = ref(props.initiallyOpen);
const toggle = () => {
isOpen.value = !isOpen.value;
};
const handleItem = (item) => {
if (typeof item?.command === "function") {
item.command();
}
};
watch(
() => props.forceOpen,
(value) => {
if (value) {
isOpen.value = true;
}
},
);
watch(isOpen, (value) => {
if (value) {
emit("opened", props.label);
} else {
emit("closed", props.label);
}
});
</script>
<template>
<div class="sidebar-speeddial" :class="{ open: isOpen }">
<button class="sidebar-button" @click="toggle" :aria-expanded="isOpen">
<component :is="icon" class="button-icon" />
<span class="button-text">{{ label }}</span>
<span class="speeddial-caret" aria-hidden="true">{{ isOpen ? "-" : "+" }}</span>
</button>
<transition name="sidebar-accordion">
<div v-show="isOpen" class="sidebar-submenu">
<button
v-for="item in items"
:key="item.label"
class="sidebar-sub-button"
@click="handleItem(item)"
>
<span class="sub-button-text">{{ item.label }}</span>
</button>
</div>
</transition>
</div>
</template>