Polish workspace organization selector
Some checks failed
Backend CI/CD / build_and_deploy (push) Has been cancelled
Frontend CI/CD / build_and_deploy (push) Has been cancelled

This commit is contained in:
2026-05-04 17:44:39 -04:00
parent 58c1301054
commit 664eb07201
2 changed files with 89 additions and 33 deletions

View File

@@ -165,7 +165,9 @@
.sidebar-workspace-menu {
@apply absolute bottom-[calc(100%+0.5rem)] left-0 right-0 z-30 flex flex-col gap-1 rounded-[1.25rem] border p-2;
background: rgba(255, 255, 255, 0.98);
isolation: isolate;
background: #fffdf8;
background-clip: padding-box;
border-color: rgba(23, 32, 51, 0.08);
box-shadow: 0 18px 40px rgba(23, 32, 51, 0.12);
}

View File

@@ -13,6 +13,7 @@
mdiChevronDown,
mdiCogOutline,
mdiPlus,
mdiSwapHorizontal,
} from '@mdi/js';
const router = useRouter();
@@ -22,6 +23,7 @@
const authStore = useAuthStore();
const isWorkspaceMenuOpen = ref(false);
const isOrganizationListOpen = ref(false);
const workspaceMenuRef = ref(null);
const activeOrganization = computed(() => organizationStore.activeOrganization);
@@ -36,6 +38,11 @@
});
const canSwitchWorkspaces = computed(() => visibleWorkspaces.value.length > 1);
const canSwitchOrganizations = computed(() => organizationStore.organizations.length > 1);
const switchableOrganizations = computed(() =>
organizationStore.organizations.filter(
organization => organization.id !== organizationStore.selectedOrganizationId
)
);
const canManageWorkspaces = computed(() =>
activeOrganization.value?.currentUserPermissions?.includes(organizationPermissions.createWorkspaces) ||
activeOrganization.value?.currentUserPermissions?.includes(organizationPermissions.manageWorkspaces) ||
@@ -57,11 +64,13 @@
}
isWorkspaceMenuOpen.value = !isWorkspaceMenuOpen.value;
isOrganizationListOpen.value = false;
}
function chooseWorkspace(workspaceId) {
workspaceStore.setActiveWorkspace(workspaceId);
isWorkspaceMenuOpen.value = false;
isOrganizationListOpen.value = false;
}
function chooseOrganization(organizationId) {
@@ -71,21 +80,29 @@
workspace => workspace.organizationId === organizationId
);
workspaceStore.setActiveWorkspace(nextWorkspace?.id ?? null);
isOrganizationListOpen.value = false;
}
function toggleOrganizationList() {
isOrganizationListOpen.value = !isOrganizationListOpen.value;
}
async function openCreateWorkspace() {
isWorkspaceMenuOpen.value = false;
isOrganizationListOpen.value = false;
await router.push({ name: 'workspace-create' });
}
async function openOrganizationSettings(organizationId) {
isWorkspaceMenuOpen.value = false;
isOrganizationListOpen.value = false;
await router.push({ name: 'organization-settings', params: { organizationId } });
}
function handleDocumentClick(event) {
if (workspaceMenuRef.value && !workspaceMenuRef.value.contains(event.target)) {
isWorkspaceMenuOpen.value = false;
isOrganizationListOpen.value = false;
}
}
@@ -154,17 +171,47 @@
<v-icon :icon="mdiPlus" />
</button>
<div class="organization-switcher">
<div class="organization-switcher-label">
<span>{{ t('workspaceSelector.organizationLabel') }}</span>
<strong>{{ activeOrganizationName }}</strong>
<div
v-if="activeOrganization"
class="organization-switcher"
>
<div class="organization-current-row">
<button
class="user-menu-item organization-current"
type="button"
@click="openOrganizationSettings(activeOrganization.id)"
>
<span class="organization-mark">{{ activeOrganization.name.slice(0, 1).toUpperCase() }}</span>
<span class="user-menu-item-copy">
<span>{{ activeOrganizationName }}</span>
<small>{{ t('workspaceSelector.organizationLabel') }}</small>
</span>
<v-icon
:icon="mdiCogOutline"
class="organization-action-icon"
/>
</button>
</div>
<button
v-for="organization in organizationStore.organizations"
v-if="canSwitchOrganizations"
class="organization-swap-button"
type="button"
:aria-expanded="isOrganizationListOpen"
@click="toggleOrganizationList"
>
<span>Change organization</span>
<v-icon :icon="mdiSwapHorizontal" />
</button>
<div
v-if="isOrganizationListOpen"
class="organization-options"
>
<button
v-for="organization in switchableOrganizations"
:key="organization.id"
class="user-menu-item organization-option"
:class="{ 'user-menu-item-active': organization.id === organizationStore.selectedOrganizationId }"
type="button"
@click="chooseOrganization(organization.id)"
>
@@ -174,16 +221,7 @@
<small>{{ t('workspaceSelector.organizationLabel') }}</small>
</span>
</button>
<button
v-if="activeOrganization"
class="user-menu-item organization-settings-link"
type="button"
@click="openOrganizationSettings(activeOrganization.id)"
>
<span>{{ t('workspaceSelector.organizationSettings') }}</span>
<v-icon :icon="mdiCogOutline" />
</button>
</div>
</div>
</div>
</div>
@@ -279,18 +317,18 @@
border-color: rgba(23, 32, 51, 0.08);
}
.organization-switcher-label {
@apply flex flex-col gap-0.5 px-3 py-2;
.organization-current-row {
@apply flex w-full min-w-0 flex-col gap-1;
}
.organization-switcher-label span {
@apply text-xs font-bold uppercase tracking-[0.18em];
color: #7a8799;
.organization-current {
@apply w-full min-w-0 border;
background: rgba(23, 32, 51, 0.04);
border-color: rgba(23, 32, 51, 0.08);
}
.organization-switcher-label strong {
@apply truncate text-sm;
color: #172033;
.organization-current:hover {
background: rgba(23, 32, 51, 0.07);
}
.organization-mark {
@@ -299,7 +337,23 @@
color: #172033;
}
.organization-settings-link {
@apply justify-between;
.organization-action-icon {
@apply flex-shrink-0 text-base;
color: #526178;
}
.organization-swap-button {
@apply flex w-full items-center justify-between gap-3 rounded-[0.9rem] border px-3 py-2.5 text-sm font-semibold transition-colors;
background: rgba(23, 32, 51, 0.04);
border-color: rgba(23, 32, 51, 0.08);
color: #172033;
}
.organization-swap-button:hover {
background: rgba(23, 32, 51, 0.08);
}
.organization-options {
@apply flex flex-col gap-1;
}
</style>