feat(common): add foundational support for dropdown-based organization switcher (#5890)

This commit is contained in:
James George 2026-02-23 20:06:10 +05:30 committed by GitHub
parent a1be60da64
commit faf2bfc8eb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 52 additions and 44 deletions

View file

@ -2219,13 +2219,12 @@
"admin": "Admin"
},
"organization_sidebar": {
"instances": "Instances",
"hoppscotch_cloud": "Hoppscotch Cloud",
"admin": "Admin",
"no_orgs_title": "No organizations yet",
"no_orgs_description": "Join or create an organization to collaborate with your team",
"error_loading": "Failed to load organizations",
"inactive_orgs": "Inactive Organizations",
"no_orgs_found": "No organizations found",
"organizations_for": "Organizations for {email}",
"multi_account_notice": "Each organization keeps its own login, using the last account accessed.",
"inactive_orgs_tooltip": "Contact support for assistance."
},

View file

@ -45,6 +45,40 @@
</template>
</tippy>
<!-- Organization Switcher (Web/Cloud) -->
<tippy
v-else-if="
platform.organization?.customOrganizationSwitcherComponent
"
interactive
trigger="click"
theme="popover"
:on-shown="() => orgSwitcherRef?.focus()"
:on-create="onOrgSwitcherCreate"
>
<HoppButtonSecondary
class="!font-bold uppercase tracking-wide !text-secondaryDark hover:bg-primaryDark focus-visible:bg-primaryDark"
:label="t('app.name')"
:icon="IconChevronDown"
reverse
/>
<template #content="{ hide }">
<div
ref="orgSwitcherRef"
class="flex flex-col focus:outline-none min-w-72"
tabindex="0"
@keyup.escape="hide()"
>
<component
:is="
platform.organization.customOrganizationSwitcherComponent
"
@close-dropdown="hide()"
/>
</div>
</template>
</tippy>
<HoppButtonSecondary
v-else
class="!font-bold uppercase tracking-wide !text-secondaryDark hover:bg-primaryDark focus-visible:bg-primaryDark"
@ -360,7 +394,9 @@ import { breakpointsTailwind, useBreakpoints, useNetwork } from "@vueuse/core"
import { useService } from "dioc/vue"
import * as TE from "fp-ts/TaskEither"
import { pipe } from "fp-ts/function"
import type { Instance } from "tippy.js"
import { computed, onMounted, reactive, ref, watch } from "vue"
import { useToast } from "~/composables/toast"
import { GetMyTeamsQuery, TeamAccessRole } from "~/helpers/backend/graphql"
import { deleteTeam as backendDeleteTeam } from "~/helpers/backend/mutations/Team"
@ -390,6 +426,16 @@ const downloadableLinksRef =
kernelMode === "web" ? ref<any | null>(null) : ref(null)
const instanceSwitcherRef =
kernelMode === "desktop" ? ref<any | null>(null) : ref(null)
const orgSwitcherRef = ref<HTMLElement | null>(null)
// Reserve scrollbar gutter so content width doesn't shift when the list
// grows long enough to scroll inside the popover's `max-h-[45vh]` container.
const onOrgSwitcherCreate = (instance: Instance) => {
const content = instance.popper?.querySelector(".tippy-content")
if (content instanceof HTMLElement) {
content.style.scrollbarGutter = "stable"
}
}
const isUserAdmin = ref(false)

View file

@ -65,14 +65,6 @@
<icon-lucide-help-circle class="svg-icons mb-4" />
{{ t("error.something_went_wrong") }}
</div>
<div v-if="showCreateOrganizationCTA" class="flex flex-col">
<hr />
<HoppButtonPrimary
:label="t('organization.create_an_organization')"
to="/orgs"
/>
</div>
</div>
<TeamsAdd
:show="showModalAdd"
@ -174,12 +166,6 @@ const isActiveWorkspace = computed(() => (id: string) => {
return workspace.value.teamID === id
})
const showCreateOrganizationCTA = computed(() => {
const { organization } = platform
return organization?.isDefaultCloudInstance ?? false
})
const switchToTeamWorkspace = (team: GetMyTeamsQuery["myTeams"][number]) => {
REMEMBERED_TEAM_ID.value = team.id
workspaceService.changeWorkspace({

View file

@ -16,16 +16,6 @@
>
<AppSidenav />
</Pane>
<Pane
v-if="showOrgSidebar"
style="width: auto; height: auto"
class="hidden !overflow-auto md:flex md:flex-col"
>
<component
:is="platform.organization.customOrganizationSidebarComponent"
/>
</Pane>
<!-- Changed to !overflow-auto to allow organization sidebar and main content to scroll independently -->
<Pane class="flex flex-1 !overflow-auto">
<Splitpanes
class="no-splitter"
@ -121,14 +111,6 @@ const rootExtensionComponents = uiExtensionService.rootUIExtensionComponents
const HAS_OPENED_SPOTLIGHT = useSetting("HAS_OPENED_SPOTLIGHT")
// Show organization sidebar if organization switching is enabled and sidebar component is provided
const showOrgSidebar = computed(() => {
return (
platform.organization?.organizationSwitchingEnabled === true &&
platform.organization.customOrganizationSidebarComponent
)
})
onBeforeMount(() => {
if (!mdAndLarger.value) {
rightSidebar.value = false

View file

@ -13,16 +13,11 @@ export type OrganizationPlatformDef = {
initiateOnboarding: () => void
/**
* Whether organization switching is enabled for this platform
* If true, an organization switcher will be shown
* Custom component for the organization switcher dropdown
* If provided, will be shown as a dropdown in the header (like the instance switcher)
* The component should emit 'close-dropdown' when the dropdown should close
*/
organizationSwitchingEnabled?: boolean
/**
* Custom component for the organization sidebar
* If provided, will be shown as a sidebar in the layout
*/
customOrganizationSidebarComponent?: Component
customOrganizationSwitcherComponent?: Component
/**
* Switch to a specific organization instance or default cloud instance