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" "admin": "Admin"
}, },
"organization_sidebar": { "organization_sidebar": {
"instances": "Instances",
"hoppscotch_cloud": "Hoppscotch Cloud", "hoppscotch_cloud": "Hoppscotch Cloud",
"admin": "Admin", "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", "error_loading": "Failed to load organizations",
"inactive_orgs": "Inactive 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.", "multi_account_notice": "Each organization keeps its own login, using the last account accessed.",
"inactive_orgs_tooltip": "Contact support for assistance." "inactive_orgs_tooltip": "Contact support for assistance."
}, },

View file

@ -45,6 +45,40 @@
</template> </template>
</tippy> </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 <HoppButtonSecondary
v-else v-else
class="!font-bold uppercase tracking-wide !text-secondaryDark hover:bg-primaryDark focus-visible:bg-primaryDark" 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 { useService } from "dioc/vue"
import * as TE from "fp-ts/TaskEither" import * as TE from "fp-ts/TaskEither"
import { pipe } from "fp-ts/function" import { pipe } from "fp-ts/function"
import type { Instance } from "tippy.js"
import { computed, onMounted, reactive, ref, watch } from "vue" import { computed, onMounted, reactive, ref, watch } from "vue"
import { useToast } from "~/composables/toast" import { useToast } from "~/composables/toast"
import { GetMyTeamsQuery, TeamAccessRole } from "~/helpers/backend/graphql" import { GetMyTeamsQuery, TeamAccessRole } from "~/helpers/backend/graphql"
import { deleteTeam as backendDeleteTeam } from "~/helpers/backend/mutations/Team" import { deleteTeam as backendDeleteTeam } from "~/helpers/backend/mutations/Team"
@ -390,6 +426,16 @@ const downloadableLinksRef =
kernelMode === "web" ? ref<any | null>(null) : ref(null) kernelMode === "web" ? ref<any | null>(null) : ref(null)
const instanceSwitcherRef = const instanceSwitcherRef =
kernelMode === "desktop" ? ref<any | null>(null) : ref(null) 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) const isUserAdmin = ref(false)

View file

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

View file

@ -16,16 +16,6 @@
> >
<AppSidenav /> <AppSidenav />
</Pane> </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"> <Pane class="flex flex-1 !overflow-auto">
<Splitpanes <Splitpanes
class="no-splitter" class="no-splitter"
@ -121,14 +111,6 @@ const rootExtensionComponents = uiExtensionService.rootUIExtensionComponents
const HAS_OPENED_SPOTLIGHT = useSetting("HAS_OPENED_SPOTLIGHT") 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(() => { onBeforeMount(() => {
if (!mdAndLarger.value) { if (!mdAndLarger.value) {
rightSidebar.value = false rightSidebar.value = false

View file

@ -13,16 +13,11 @@ export type OrganizationPlatformDef = {
initiateOnboarding: () => void initiateOnboarding: () => void
/** /**
* Whether organization switching is enabled for this platform * Custom component for the organization switcher dropdown
* If true, an organization switcher will be shown * 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 customOrganizationSwitcherComponent?: Component
/**
* Custom component for the organization sidebar
* If provided, will be shown as a sidebar in the layout
*/
customOrganizationSidebarComponent?: Component
/** /**
* Switch to a specific organization instance or default cloud instance * Switch to a specific organization instance or default cloud instance